diff --git a/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.cpp b/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.cpp index 17a9cc1ba9..0aff1cd171 100644 --- a/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.cpp +++ b/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.cpp @@ -1322,11 +1322,10 @@ _ToMaterialNetworkMap( } VtValue -HdSceneIndexAdapterSceneDelegate::GetMaterialResource(SdfPath const & id) +HdSceneIndexAdapterSceneDelegate::GetMaterialResourceFromSceneIndexPrim( + HdSceneIndexPrim& prim, const TfTokenVector& renderContexts) { TRACE_FUNCTION(); - HF_MALLOC_TAG_FUNCTION(); - HdSceneIndexPrim prim = _GetInputPrim(id); HdMaterialSchema matSchema = HdMaterialSchema::GetFromParent( prim.dataSource); @@ -1335,9 +1334,8 @@ HdSceneIndexAdapterSceneDelegate::GetMaterialResource(SdfPath const & id) } // Query for a material network to match the requested render contexts - const TfTokenVector renderContexts = - GetRenderIndex().GetRenderDelegate()->GetMaterialRenderContexts(); - HdMaterialNetworkSchema netSchema = matSchema.GetMaterialNetwork(renderContexts); + HdMaterialNetworkSchema netSchema = + matSchema.GetMaterialNetwork(renderContexts); if (!netSchema.IsDefined()) { return VtValue(); } @@ -1345,6 +1343,18 @@ HdSceneIndexAdapterSceneDelegate::GetMaterialResource(SdfPath const & id) return VtValue(_ToMaterialNetworkMap(netSchema, renderContexts)); } +VtValue +HdSceneIndexAdapterSceneDelegate::GetMaterialResource(SdfPath const & id) +{ + TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + HdSceneIndexPrim prim = _GetInputPrim(id); + return GetMaterialResourceFromSceneIndexPrim( + prim, + GetRenderIndex().GetRenderDelegate()->GetMaterialRenderContexts()); +} + static TfTokenVector _ToTokenVector(const std::vector &strings) diff --git a/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.h b/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.h index 20a95deea2..016b587747 100644 --- a/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.h +++ b/pxr/imaging/hd/sceneIndexAdapterSceneDelegate.h @@ -151,6 +151,9 @@ class HdSceneIndexAdapterSceneDelegate // ------------------------------------------------------------------------ // Material API + static HD_API VtValue GetMaterialResourceFromSceneIndexPrim( + HdSceneIndexPrim& prim, const TfTokenVector& renderContexts); + SdfPath GetMaterialId(SdfPath const &id) override; VtValue GetMaterialResource(SdfPath const &id) override; HdIdVectorSharedPtr GetCoordSysBindings(SdfPath const &id) override; diff --git a/pxr/imaging/hdSt/CMakeLists.txt b/pxr/imaging/hdSt/CMakeLists.txt index 7f05abfbbc..1c951cf53b 100644 --- a/pxr/imaging/hdSt/CMakeLists.txt +++ b/pxr/imaging/hdSt/CMakeLists.txt @@ -26,6 +26,7 @@ if (${PXR_ENABLE_MATERIALX_SUPPORT}) list(APPEND optionalPrivateClasses materialXFilter materialXShaderGen + materialXSyncSceneIndex ) endif() if (PXR_ENABLE_PTEX_SUPPORT) diff --git a/pxr/imaging/hdSt/material.cpp b/pxr/imaging/hdSt/material.cpp index 74c24af5f9..94d8eca95a 100644 --- a/pxr/imaging/hdSt/material.cpp +++ b/pxr/imaging/hdSt/material.cpp @@ -19,6 +19,11 @@ #include "pxr/imaging/hdSt/tokens.h" #include "pxr/imaging/hdSt/materialParam.h" +#ifdef PXR_MATERIALX_SUPPORT_ENABLED +#include "pxr/imaging/hdSt/materialXSyncSceneIndex.h" +#include "pxr/imaging/hdSt/renderDelegate.h" +#endif + #include "pxr/imaging/hd/changeTracker.h" #include "pxr/imaging/hd/tokens.h" @@ -172,48 +177,91 @@ HdStMaterial::_ProcessTextureDescriptors( /* virtual */ void HdStMaterial::Sync(HdSceneDelegate *sceneDelegate, - HdRenderParam *renderParam, - HdDirtyBits *dirtyBits) + HdRenderParam *renderParam, + HdDirtyBits *dirtyBits) { HD_TRACE_FUNCTION(); HF_MALLOC_TAG_FUNCTION(); + const HdDirtyBits bits = *dirtyBits; + + if (!(bits & DirtyResource) && !(bits & DirtyParams)) { + *dirtyBits = Clean; + return; + } + + bool processedMaterialNetwork = false; + HdStResourceRegistrySharedPtr const& resourceRegistry = std::static_pointer_cast( sceneDelegate->GetRenderIndex().GetResourceRegistry()); - HdDirtyBits bits = *dirtyBits; +#ifdef PXR_MATERIALX_SUPPORT_ENABLED + { + HdStRenderDelegate* stormDelegate = static_cast( + sceneDelegate->GetRenderIndex().GetRenderDelegate()); - if (!(bits & DirtyResource) && !(bits & DirtyParams)) { - *dirtyBits = Clean; - return; + HdSt_MaterialFilterTaskSharedPtr filterTask; + + // This scene index manages early parallel MaterialX codegen. It exists + // when that optimization is enabled. + if (HdSt_MaterialXSyncSceneIndex* sceneIndex = + stormDelegate->GetMaterialXSyncSceneIndex()) { + + // Wait for all early parallel codegen tasks to complete and + // retrieve the state cached for this sprim when codegen was + // started + filterTask = sceneIndex->WaitAndExtractFilterTask(GetId()); + } + + if (filterTask) { + // We only use filter tasks for non-volume materials + constexpr bool isVolume = false; + + _hdStMaterialNetwork.ProcessFilterTask( + GetId(), filterTask, + isVolume, resourceRegistry.get()); + + processedMaterialNetwork = true; + } } +#endif - bool markBatchesDirty = false; + // The serial MaterialX codegen code path - executed only if we haven't + // already processed the material network above + if (!processedMaterialNetwork) { + VtValue vtMat = sceneDelegate->GetMaterialResource(GetId()); + if (vtMat.IsHolding()) { + + HdMaterialNetworkMap const& hdNetworkMap = + vtMat.UncheckedGet(); + + if (!hdNetworkMap.terminals.empty() && !hdNetworkMap.map.empty()) { + _hdStMaterialNetwork.ProcessMaterialNetwork(GetId(), + hdNetworkMap, resourceRegistry.get()); + + processedMaterialNetwork = true; + } + } + } std::string fragmentSource; std::string displacementSource; std::string volumeSource; + VtDictionary materialMetadata; TfToken materialTag = _materialTag; HdSt_MaterialParamVector params; HdStMaterialNetwork::TextureDescriptorVector textureDescriptors; - VtValue vtMat = sceneDelegate->GetMaterialResource(GetId()); - if (vtMat.IsHolding()) { - HdMaterialNetworkMap const& hdNetworkMap = - vtMat.UncheckedGet(); - if (!hdNetworkMap.terminals.empty() && !hdNetworkMap.map.empty()) { - _networkProcessor.ProcessMaterialNetwork(GetId(), hdNetworkMap, - resourceRegistry.get()); - fragmentSource = _networkProcessor.GetFragmentCode(); - volumeSource = _networkProcessor.GetVolumeCode(); - displacementSource = _networkProcessor.GetDisplacementCode(); - materialMetadata = _networkProcessor.GetMetadata(); - materialTag = _networkProcessor.GetMaterialTag(); - params = _networkProcessor.GetMaterialParams(); - textureDescriptors = _networkProcessor.GetTextureDescriptors(); - } + if (processedMaterialNetwork) { + fragmentSource = _hdStMaterialNetwork.GetFragmentCode(); + volumeSource = _hdStMaterialNetwork.GetVolumeCode(); + displacementSource = _hdStMaterialNetwork.GetDisplacementCode(); + materialMetadata = _hdStMaterialNetwork.GetMetadata(); + materialTag = _hdStMaterialNetwork.GetMaterialTag(); + params = _hdStMaterialNetwork.GetMaterialParams(); + textureDescriptors = _hdStMaterialNetwork.GetTextureDescriptors(); } // Use fallback shader when there is no source for @@ -226,6 +274,8 @@ HdStMaterial::Sync(HdSceneDelegate *sceneDelegate, materialMetadata = _fallbackGlslfx->GetMetadata(); } + bool markBatchesDirty = false; + // Update volume material data. if (_volumeMaterialData.source != volumeSource) { // If we're updating the volume source, we need to rebatch anything that diff --git a/pxr/imaging/hdSt/material.h b/pxr/imaging/hdSt/material.h index 2bcb6ca6b1..10117f1c85 100644 --- a/pxr/imaging/hdSt/material.h +++ b/pxr/imaging/hdSt/material.h @@ -8,9 +8,11 @@ #define PXR_IMAGING_HD_ST_MATERIAL_H #include "pxr/pxr.h" + #include "pxr/imaging/hdSt/api.h" #include "pxr/imaging/hdSt/materialNetwork.h" #include "pxr/imaging/hdSt/shaderCode.h" + #include "pxr/imaging/hd/material.h" #include "pxr/imaging/hf/perfLog.h" @@ -123,7 +125,7 @@ class HdStMaterial final: public HdMaterial TfToken _materialTag; size_t _textureHash; - HdStMaterialNetwork _networkProcessor; + HdStMaterialNetwork _hdStMaterialNetwork; }; inline bool HdStMaterial::HasPtex() const diff --git a/pxr/imaging/hdSt/materialNetwork.cpp b/pxr/imaging/hdSt/materialNetwork.cpp index 123c25a80d..48aa597442 100644 --- a/pxr/imaging/hdSt/materialNetwork.cpp +++ b/pxr/imaging/hdSt/materialNetwork.cpp @@ -159,8 +159,8 @@ _GetGlslfxForTerminal( } } -static HdMaterialNode2 const* -_GetTerminalNode( +HdMaterialNode2 const* +HdSt_GetTerminalNode( HdMaterialNetwork2 const& network, TfToken const& terminalName, SdfPath * terminalNodePath) @@ -1100,57 +1100,85 @@ HdStMaterialNetwork::ProcessMaterialNetwork( { HD_TRACE_FUNCTION(); - _fragmentSource.clear(); - _displacementSource.clear(); - _materialMetadata.clear(); - _materialParams.clear(); - _textureDescriptors.clear(); - _materialTag = HdStMaterialTagTokens->defaultMaterialTag; + bool isVolume = false; + + auto filterTask = std::make_shared(); + filterTask->hdNetwork = + HdConvertToHdMaterialNetwork2(hdNetworkMap, &isVolume); // The fragment source comes from the 'surface' network or the // 'volume' network. - bool isVolume = false; HdMaterialNetwork2 surfaceNetwork = HdConvertToHdMaterialNetwork2(hdNetworkMap, &isVolume); const TfToken &terminalName = (isVolume) ? HdMaterialTerminalTokens->volume : HdMaterialTerminalTokens->surface; - SdfPath surfTerminalPath; - if (HdMaterialNode2 const* surfTerminal = - _GetTerminalNode(surfaceNetwork, terminalName, &surfTerminalPath)) { + filterTask->terminalNode = + HdSt_GetTerminalNode( + filterTask->hdNetwork, + terminalName, + &filterTask->terminalNodePath); + + if (!filterTask->terminalNode) { + return; + } + + ProcessFilterTask(materialId, filterTask, isVolume, resourceRegistry); +} + +void +HdStMaterialNetwork::ProcessFilterTask( + SdfPath const& materialId, + HdSt_MaterialFilterTaskSharedPtr filterTask, + bool isVolume, + HdStResourceRegistry *resourceRegistry) +{ + HD_TRACE_FUNCTION(); + + _fragmentSource.clear(); + _displacementSource.clear(); + _materialMetadata.clear(); + _materialParams.clear(); + _textureDescriptors.clear(); + + _materialTag = HdStMaterialTagTokens->defaultMaterialTag; + + if (!filterTask || !filterTask->terminalNode) { + return; + } #ifdef PXR_MATERIALX_SUPPORT_ENABLED - if (!isVolume) { - _materialXGfx = HdSt_ApplyMaterialXFilter(&surfaceNetwork, materialId, - *surfTerminal, surfTerminalPath, - &_materialParams, resourceRegistry); - } + if (!isVolume) { + _materialXGfx = HdSt_ApplyMaterialXFilter(filterTask, materialId, + &_materialParams, resourceRegistry); + } #endif - // Extract the glslfx and metadata for surface/volume. - _GetGlslfxForTerminal(_surfaceGfx, &_surfaceGfxHash, - surfTerminal->nodeTypeId, resourceRegistry); - if (_surfaceGfx) { - - // If the glslfx file is not valid we skip parsing the network. - // This produces no fragmentSource which means Storm's material - // will use the fallback shader. - if (_surfaceGfx->IsValid()) { - - _fragmentSource = _surfaceGfx->GetSurfaceSource(); - _volumeSource = _surfaceGfx->GetVolumeSource(); - - _materialMetadata = _surfaceGfx->GetMetadata(); - _materialTag = _GetMaterialTag(_materialMetadata, *surfTerminal); - _GatherMaterialParams(surfaceNetwork, *surfTerminal, - &_materialParams, &_textureDescriptors, - _materialTag); - - // OSL networks have a displacement network in hdNetworkMap - // under terminal: HdMaterialTerminalTokens->displacement. - // For Storm however we expect the displacement shader to be - // provided via the surface glslfx / terminal. - _displacementSource = _surfaceGfx->GetDisplacementSource(); - } + // Extract the glslfx and metadata for surface/volume. + _GetGlslfxForTerminal(_surfaceGfx, &_surfaceGfxHash, + filterTask->terminalNode->nodeTypeId, resourceRegistry); + if (_surfaceGfx) { + + // If the glslfx file is not valid we skip parsing the network. + // This produces no fragmentSource which means Storm's material + // will use the fallback shader. + if (_surfaceGfx->IsValid()) { + _fragmentSource = _surfaceGfx->GetSurfaceSource(); + _volumeSource = _surfaceGfx->GetVolumeSource(); + + _materialMetadata = _surfaceGfx->GetMetadata(); + + _materialTag = _GetMaterialTag( + _surfaceGfx->GetMetadata(), *filterTask->terminalNode); + _GatherMaterialParams( + filterTask->hdNetwork, *filterTask->terminalNode, + &_materialParams, &_textureDescriptors, + _materialTag); + + // OSL networks have a displacement network in hdNetworkMap + // under terminal: HdMaterialTerminalTokens->displacement. + // For Storm however we expect the displacement shader to be + // provided via the surface glslfx / terminal. + _displacementSource = _surfaceGfx->GetDisplacementSource(); } } } diff --git a/pxr/imaging/hdSt/materialNetwork.h b/pxr/imaging/hdSt/materialNetwork.h index 4d8292ff51..df30b90e2c 100644 --- a/pxr/imaging/hdSt/materialNetwork.h +++ b/pxr/imaging/hdSt/materialNetwork.h @@ -20,9 +20,57 @@ PXR_NAMESPACE_OPEN_SCOPE +class SdrRegistry; class HdStResourceRegistry; using HioGlslfxSharedPtr = std::shared_ptr; using HdSt_MaterialParamVector = std::vector; +struct HdSt_MaterialFilterTask; +using HdSt_MaterialFilterTaskSharedPtr = + std::shared_ptr; + +extern HdMaterialNode2 const* +HdSt_GetTerminalNode( + HdMaterialNetwork2 const& network, + TfToken const& terminalName, + SdfPath * terminalNodePath); + +/// Encapsulates the input data for MaterialX codegen as well as metadata +/// necessary for completing `HdStMaterial::Sync` based on the result of the +/// codegen. +/// This object can either live on the stack, if MaterialX codegen happens +/// synchronously, or on the heap, owned by the respective Sprim, if the +/// codegen happens in parallel tasks. +/// +struct ARCH_EXPORT_TYPE HdSt_MaterialFilterTask final +{ + HdMaterialNetwork2 hdNetwork; + HdMaterialNode2 const* terminalNode = nullptr; // pointer to a node + // in the above network + SdfPath terminalNodePath; // path to the above node + +#ifdef PXR_MATERIALX_SUPPORT_ENABLED + // Stores the mappings between the node paths in the original + // HdMaterialNetwork to the corresponding anonymized node paths + using OrigToAnonSdfPathMap = + std::unordered_map; + OrigToAnonSdfPathMap origToAnonSdfPathMap; + + /// Build the `anonNetwork`, equivalent to the given hdNetwork but anonymized + /// and stripped of non-topological parameters to better re-use the generated + /// shader. + /// Returns the hash of the anonymized network. + size_t BuildAnonymizedMaterialNetwork( + HdMaterialNetwork2* anonNetwork); + + void AddFallbackDomeLightTextureNode(); + + void AddMaterialXParams( + MaterialX::Shader const& mxShader, + HdSt_MaterialParamVector* materialParams); + + bool IsMaterialX(SdrRegistry* sdrRegistry) const; +#endif +}; /// \class HdStMaterialNetwork /// @@ -37,6 +85,14 @@ class HdStMaterialNetwork final HDST_API ~HdStMaterialNetwork(); + /// Process the necessary network information cached in the filter task. + HDST_API + void ProcessFilterTask( + SdfPath const& materialId, + HdSt_MaterialFilterTaskSharedPtr filterTask, + bool isVolume, + HdStResourceRegistry *resourceRegistry); + /// Process a material network topology and extract all the information we /// need from it. HDST_API @@ -67,7 +123,7 @@ class HdStMaterialNetwork final struct TextureDescriptor { // Name by which the texture will be accessed, i.e., the name - // of the accesor for thexture will be HdGet_name(...). + // of the accessor for the texture will be HdGet_name(...). // It is generated from the input name the corresponding texture // node is connected to. TfToken name; diff --git a/pxr/imaging/hdSt/materialXFilter.cpp b/pxr/imaging/hdSt/materialXFilter.cpp index 65ceec23a5..49555edb78 100644 --- a/pxr/imaging/hdSt/materialXFilter.cpp +++ b/pxr/imaging/hdSt/materialXFilter.cpp @@ -14,6 +14,7 @@ #include "pxr/imaging/hdMtlx/hdMtlx.h" #include "pxr/imaging/hdMtlx/tokens.h" #include "pxr/imaging/hgi/tokens.h" +#include "pxr/imaging/hd/tokens.h" #include "pxr/usd/sdf/schema.h" #include "pxr/usd/sdr/registry.h" @@ -131,13 +132,6 @@ TF_DEFINE_PRIVATE_TOKENS( (vaddressmode) ); -namespace -{ -// To store the mapping between the node paths in the HdMaterialNetwork to -// the corresponding anonymized node paths - -using _AnonNodePathMap = std::unordered_map; -} - //////////////////////////////////////////////////////////////////////////////// // Shader Gen Functions @@ -390,10 +384,8 @@ _AddDefaultMtlxTextureValues( } } -static void -_AddFallbackDomeLightTextureNode( - HdMaterialNetwork2* hdNetwork, - SdfPath const& terminalNodePath) +void +HdSt_MaterialFilterTask::AddFallbackDomeLightTextureNode() { // Create and add a Fallback Dome Light Texture Node to the hdNetwork HdMaterialNode2 hdDomeTextureNode; @@ -404,13 +396,13 @@ _AddFallbackDomeLightTextureNode( HdStPackageFallbackDomeLightTexture())); const SdfPath domeTexturePath = terminalNodePath.ReplaceName(_tokens->domeLightFallback); - hdNetwork->nodes.insert({domeTexturePath, hdDomeTextureNode}); + hdNetwork.nodes.insert({domeTexturePath, hdDomeTextureNode}); // Connect the new Texture Node to the Terminal Node HdMaterialConnection2 domeTextureConn; domeTextureConn.upstreamNode = domeTexturePath; domeTextureConn.upstreamOutputName = domeTexturePath.GetNameToken(); - hdNetwork->nodes[terminalNodePath]. + hdNetwork.nodes[terminalNodePath]. inputConnections[domeTextureConn.upstreamOutputName] = {domeTextureConn}; } @@ -768,6 +760,8 @@ _GetMaterialTag( HdMaterialNetwork2 const& hdNetwork, HdMaterialNode2 const& terminal) { + HD_TRACE_FUNCTION(); + // Return the custom material tag if specified in the config Dictionary. const auto tagIt = hdNetwork.config.find(_tokens->mtlxMaterialTag); if (tagIt != hdNetwork.config.end()) { @@ -905,7 +899,7 @@ _ConnectPrimvarNodesToTerminalNode( } } -// Returns the default texture cordinate name from the textureNodes sdr metadata +// Returns the default texture coordinate name from the textureNodes Sdr metadata // if no name was specified return 'st' static TfToken _GetDefaultTexcoordName() @@ -1092,33 +1086,27 @@ _ReplaceFilenameInput( mxFilenameInputName.c_str(), mxNodeDef->getName().c_str()); } -// Gather the Material Params from the glslfx ShaderPtr +// Gather the Material Params from the MaterialX shader void -_AddMaterialXParams( - mx::ShaderPtr const& glslfxShader, - HdMaterialNetwork2* hdNetwork, - SdfPath const& terminalNodePath, - _AnonNodePathMap const& hdToAnonNodePathMap, +HdSt_MaterialFilterTask::AddMaterialXParams( + mx::Shader const& mxShader, 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); + _ConnectPrimvarNodesToTerminalNode(terminalNodePath, &hdNetwork); // Store all the parameter values, mapped by the anonymized names used // for MaterialXShaderGen // std::map mxParamNameToValue; - for (auto const& [nodePath, hdNode]: hdNetwork->nodes) { + for (auto const& [nodePath, hdNode]: hdNetwork.nodes) { // Terminal Node parameters are not prefixed. std::string anonNodeNamePrefix; if (nodePath != terminalNodePath) { - const auto anonNodePathIt = hdToAnonNodePathMap.find(nodePath); - if (anonNodePathIt != hdToAnonNodePathMap.end()) { + const auto anonNodePathIt = origToAnonSdfPathMap.find(nodePath); + if (anonNodePathIt != origToAnonSdfPathMap.end()) { anonNodeNamePrefix = anonNodePathIt->second.GetName() + "_"; } } @@ -1135,14 +1123,14 @@ _AddMaterialXParams( // 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 anonToHdNodePathMap; - for (auto const& [hdPath, anonPath] : hdToAnonNodePathMap) { + std::map anonToOrigSdfPathMap; + for (auto const& [hdPath, anonPath] : origToAnonSdfPathMap) { if (hdPath != terminalNodePath) { - anonToHdNodePathMap.emplace(anonPath.GetName(), hdPath); + anonToOrigSdfPathMap.emplace(anonPath.GetName(), hdPath); } } - const mx::ShaderStage& pxlStage = glslfxShader->getStage(mx::Stage::PIXEL); + const mx::ShaderStage& pxlStage = mxShader.getStage(mx::Stage::PIXEL); const auto& paramsBlock = pxlStage.getUniformBlock(mx::HW::PUBLIC_UNIFORMS); for (size_t i = 0; i < paramsBlock.size(); ++i) { @@ -1250,44 +1238,66 @@ _AddMaterialXParams( } // Get the original hdNodeName from the MaterialX node name - const auto hdNodePathIt = anonToHdNodePathMap.find(anonNodeName); - if (hdNodePathIt != anonToHdNodePathMap.end()) { + const auto hdNodePathIt = anonToOrigSdfPathMap.find(anonNodeName); + if (hdNodePathIt != anonToOrigSdfPathMap.end()) { _UpdateTextureNode( - param.name, hdNetwork, + 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, mxParamName); + _ReplaceFilenameInput( + &hdNetwork, terminalNodePath, variable->getVariable()); } } } } -static mx::ShaderPtr -_GenerateMaterialXShader( - HdMaterialNetwork2 const& hdNetwork, +HdSt_MaterialXGeneratorTask::HdSt_MaterialXGeneratorTask( + HdSt_MaterialFilterTaskSharedPtr filterTask, SdfPath const& materialPath, - HdMaterialNode2 const& terminalNode, - SdfPath const& terminalNodePath, - TfToken const& materialTagToken, - TfToken const& apiName, - bool const bindlessTexturesEnabled) + Hgi const& hgi) + : _filterTask(filterTask) + , _terminalNode(*filterTask->terminalNode) + , _materialPath(materialPath) + , _bindlessTexturesEnabled(hgi.GetCapabilities()->IsSet( + HgiDeviceCapabilitiesBitsBindlessTextures)) + , _apiName(hgi.GetAPIName()) + , _strMaterialTag(_GetMaterialTag( + filterTask->hdNetwork, *filterTask->terminalNode)) { - TF_DEBUG(HDST_MTLX).Msg("\nGenerate MaterialX Shader for:\n" - " - <%s> material\n - bindless textures %s enabled\n" - " - '%s' api\n - '%s' materialTag.\n\n", - materialPath.GetAsString().c_str(), - (bindlessTexturesEnabled ? "" : "not"), apiName.GetText(), - materialTagToken.GetText()); + // Anonymize the network to make sure shader code does not depend + // on node names + size_t const topoHash = + filterTask->BuildAnonymizedMaterialNetwork(&_hdNetwork); + + _terminalNodePath = filterTask->origToAnonSdfPathMap[filterTask->terminalNodePath]; + + Tf_HashState hashState; + TfHashAppend(hashState, topoHash); + TfHashAppend(hashState, _strMaterialTag); + _shaderHash = hashState.GetCode(); +} + +mx::ShaderPtr +HdSt_MaterialXGeneratorTask::Generate() const +{ + HD_TRACE_FUNCTION(); + + TF_DEBUG(HDST_MTLX).Msg("Generate MaterialX Shader for <%s> material.\n" + " - bindless textures %s enabled\n - '%s' api\n - '%s' materialTag.\n", + _materialPath.GetAsString().c_str(), + (_bindlessTexturesEnabled ? "" : "not"), _apiName.GetText(), + _strMaterialTag.c_str()); // Create the MaterialX Document from the HdMaterialNetwork const mx::DocumentPtr& stdLibraries = HdMtlxStdLibraries(); HdMtlxTexturePrimvarData hdMtlxData; + const mx::DocumentPtr mtlxDoc = HdMtlxCreateMtlxDocumentFromHdNetwork( - hdNetwork, terminalNode, terminalNodePath, materialPath, + _hdNetwork, _terminalNode, _terminalNodePath, _materialPath, stdLibraries, &hdMtlxData); // Add domelight and other textures to mxHdInfo so the proper entry points @@ -1295,18 +1305,18 @@ _GenerateMaterialXShader( HdSt_MxShaderGenInfo mxHdInfo; _UpdateMxHdTextureNames( hdMtlxData.hdTextureNodes, hdMtlxData.mxHdTextureMap, - terminalNode, terminalNodePath, &mxHdInfo.textureNames); + _terminalNode, _terminalNodePath, &mxHdInfo.textureNames); _UpdatePrimvarNodes( - mtlxDoc, hdNetwork, hdMtlxData.hdPrimvarNodes, + mtlxDoc, _hdNetwork, hdMtlxData.hdPrimvarNodes, &mxHdInfo.primvarMap, &mxHdInfo.primvarDefaultValueMap); - mxHdInfo.materialTag = materialTagToken.GetString(); - mxHdInfo.bindlessTexturesEnabled = bindlessTexturesEnabled; + mxHdInfo.materialTag = _strMaterialTag; + mxHdInfo.bindlessTexturesEnabled = _bindlessTexturesEnabled; // Generate the glslfx source code from the mtlxDoc return HdSt_GenMaterialXShader( - mtlxDoc, stdLibraries, HdMtlxSearchPaths(), mxHdInfo, apiName); + mtlxDoc, stdLibraries, HdMtlxSearchPaths(), mxHdInfo, _apiName); } //////////////////////////////////////////////////////////////////////////////// @@ -1352,20 +1362,18 @@ _SanitizeName(std::string const& name) return sanitizedName; } -// Build the topoNetwork, equivalent to the given hdNetwork but anonymized and -// stripped of non-topological parameters to better re-use the generated shader. -static size_t -_BuildEquivalentMaterialNetwork( - HdMaterialNetwork2 const& hdNetwork, - HdMaterialNetwork2* topoNetwork, - _AnonNodePathMap* anonNodePathMap) +size_t +HdSt_MaterialFilterTask::BuildAnonymizedMaterialNetwork( + HdMaterialNetwork2* anonNetwork) { + HD_TRACE_FUNCTION(); + // 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. - anonNodePathMap->clear(); + origToAnonSdfPathMap.clear(); // Anonymized paths will be of the form: // /NG_Anonymized/N0, /NG_Anonymized/N1, /NG_Anonymized/N2... @@ -1373,7 +1381,7 @@ _BuildEquivalentMaterialNetwork( // 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 + // at the terminals. This provides a stable traversal and consistent // anonymized renaming that will not be affected by the ordering of the // SdfPaths in the hdNetwork. size_t nodeCounter = 0; @@ -1391,8 +1399,8 @@ _BuildEquivalentMaterialNetwork( // and remove any underscores this is to help with texture nodes // later in _AddMaterialXParams() const std::string sanitizedName = _SanitizeName(path->GetName()); - if (!anonNodePathMap->count(SdfPath(sanitizedName))) { - (*anonNodePathMap)[*path] = SdfPath(sanitizedName); + if (!origToAnonSdfPathMap.count(SdfPath(sanitizedName))) { + origToAnonSdfPathMap[*path] = SdfPath(sanitizedName); TF_DEBUG(HDST_MTLX).Msg( " - Map node named '%s' (sanitized to %s) to <%s> " "(full path)\n", @@ -1411,14 +1419,14 @@ _BuildEquivalentMaterialNetwork( // When using anonymized networks we map the full path name to the // new anonymized path. const HdMaterialNode2& node = hdNetwork.nodes.find(*path)->second; - if (!anonNodePathMap->count(*path)) { + if (!origToAnonSdfPathMap.count(*path)) { const std::string anonNodeName = "N" + std::to_string(nodeCounter++) + _SanitizeName(node.nodeTypeId.GetString()) + _SanitizeName(input.GetString()); const SdfPath anonPath = anonBaseName.AppendChild(TfToken(anonNodeName)); - (*anonNodePathMap)[*path] = anonPath; + origToAnonSdfPathMap[*path] = anonPath; TF_DEBUG(HDST_MTLX).Msg( " - Map node <%s> to <%s> (anonymized path)\n", path->GetText(), anonPath.GetText()); @@ -1435,14 +1443,16 @@ _BuildEquivalentMaterialNetwork( // Copy the incoming hdNetwork to the topoNetwork using only the // anonymized names - topoNetwork->primvars = hdNetwork.primvars; - topoNetwork->config = hdNetwork.config; - for (const auto& [terminalName, terminalConn] : hdNetwork.terminals) { - topoNetwork->terminals.emplace( - terminalName, + + anonNetwork->primvars = hdNetwork.primvars; + anonNetwork->config = hdNetwork.config; + + for (const auto& terminal : hdNetwork.terminals) { + anonNetwork->terminals.emplace( + terminal.first, HdMaterialConnection2 { - (*anonNodePathMap)[terminalConn.upstreamNode], - terminalConn.upstreamOutputName }); + origToAnonSdfPathMap[terminal.second.upstreamNode], + terminal.second.upstreamOutputName }); } for (const auto& [inNodePath, inNode] : hdNetwork.nodes) { HdMaterialNode2 outNode; @@ -1499,22 +1509,22 @@ _BuildEquivalentMaterialNetwork( for (const auto& inConn : connPair.second) { outConn.emplace_back( HdMaterialConnection2 { - (*anonNodePathMap)[inConn.upstreamNode], + origToAnonSdfPathMap[inConn.upstreamNode], inConn.upstreamOutputName }); } outNode.inputConnections.emplace(connPair.first, std::move(outConn)); } - topoNetwork->nodes.emplace( - (*anonNodePathMap)[inNodePath], std::move(outNode)); + anonNetwork->nodes.emplace( + origToAnonSdfPathMap[inNodePath], std::move(outNode)); } - // Build the topo hash from the topo network + // Build the topo hash from the anonymized network Tf_HashState topoHash; - for (const auto& terminal : topoNetwork->terminals) { + for (const auto& terminal : anonNetwork->terminals) { TfHashAppend(topoHash, terminal.first); TfHashAppend(topoHash, terminal.second.upstreamNode.GetName()); } - for (const auto& node : topoNetwork->nodes) { + for (const auto& node : anonNetwork->nodes) { TfHashAppend(topoHash, node.first.GetName()); TfHashAppend(topoHash, node.second.nodeTypeId); for (const auto& param : node.second.parameters) { @@ -1533,93 +1543,78 @@ _BuildEquivalentMaterialNetwork( return topoHash.GetCode(); } +bool +HdSt_MaterialFilterTask::IsMaterialX(SdrRegistry* sdrRegistry) const +{ + const SdrShaderNodeConstPtr mtlxSdrNode = + sdrRegistry->GetShaderNodeByIdentifierAndType( + terminalNode->nodeTypeId, + _tokens->mtlx); + + return !!mtlxSdrNode; +} mx::ShaderPtr HdSt_ApplyMaterialXFilter( - HdMaterialNetwork2* hdNetwork, + HdSt_MaterialFilterTaskSharedPtr filterTask, SdfPath const& materialPath, - HdMaterialNode2 const& terminalNode, - SdfPath const& terminalNodePath, HdSt_MaterialParamVector* materialParams, HdStResourceRegistry *resourceRegistry) { - // Check if the Terminal is a MaterialX Node SdrRegistry &sdrRegistry = SdrRegistry::GetInstance(); - const SdrShaderNodeConstPtr mtlxSdrNode = - sdrRegistry.GetShaderNodeByIdentifierAndType(terminalNode.nodeTypeId, - _tokens->mtlx); + + if (!filterTask->IsMaterialX(&sdrRegistry)) { + return nullptr; + } + + const SdrShaderNodeConstPtr mtlxSdrNode = + sdrRegistry.GetShaderNodeByIdentifierAndType( + filterTask->terminalNode->nodeTypeId, + _tokens->mtlx); + if (!mtlxSdrNode) { return nullptr; } TRACE_FUNCTION_SCOPE("ApplyMaterialXFilter: Found Mtlx Node.") - // Anonymize the network to make sure shader code does not depend - // on node names - TF_DEBUG(HDST_MTLX).Msg("Build Anonymous Material Network for <%s>.\n", - terminalNodePath.GetAsString().c_str()); - _AnonNodePathMap anonNodePathMap; - HdMaterialNetwork2 anonNetwork; - size_t topoHash = _BuildEquivalentMaterialNetwork( - *hdNetwork, &anonNetwork, &anonNodePathMap); - SdfPath anonTerminalNodePath = anonNodePathMap[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()) { + HdSt_MaterialXGeneratorTask generatorTask( + filterTask, + materialPath, + *resourceRegistry->GetHgi()); + + HdInstance mxShaderInstance = + resourceRegistry->RegisterMaterialXShader( + generatorTask.GetShaderHash()); + + mx::ShaderPtr mxShader; + if (mxShaderInstance.IsFirstInstance()) { try { - glslfxShader = _GenerateMaterialXShader( - anonNetwork, materialPath, terminalNode, - anonTerminalNodePath, materialTag, apiName, - bindlessTexturesEnabled); + mxShader = generatorTask.Generate(); } 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); + mxShaderInstance.SetValue(mxShader); } else { // Get the mx::ShaderPtr from the resource registry - glslfxShader = glslfxInstance.GetValue(); - TF_DEBUG(HDST_MTLX).Msg("Use previously generated MaterialX shader for " - "material <%s>.\n", materialPath.GetAsString().c_str()); + mxShader = mxShaderInstance.GetValue(); } // Add a Fallback DomeLight texture node to the network - _AddFallbackDomeLightTextureNode(hdNetwork, terminalNodePath); + filterTask->AddFallbackDomeLightTextureNode(); - // Add material parameters from the original network - _AddMaterialXParams( - glslfxShader, hdNetwork, terminalNodePath, - anonNodePathMap, materialParams); + // Create a new terminal node with the mxShader + if (mxShader) { + // Add material parameters from the original network + filterTask->AddMaterialXParams(*mxShader, materialParams); - // Create a new terminal node with the glslfxShader - if (glslfxShader) { const std::string glslfxSourceCode = - glslfxShader->getSourceCode(mx::Stage::PIXEL); - if (TfDebug::IsEnabled(HDST_MTLX_DUMP_SHADER_SOURCEFILE)) { - const std::string filename = materialPath.GetName() + ".glslfx"; - std::fstream output(filename.c_str(), std::ios::out); - output << glslfxSourceCode; - output.close(); - fprintf(stdout, "Write MaterialX glslfx shader: '%s'\n", - filename.c_str()); - } + mxShader->getSourceCode(mx::Stage::PIXEL); + SdrShaderNodeConstPtr sdrNode = sdrRegistry.GetShaderNodeFromSourceCode( glslfxSourceCode, @@ -1627,14 +1622,60 @@ HdSt_ApplyMaterialXFilter( mtlxSdrNode->GetMetadata()); HdMaterialNode2 newTerminalNode; newTerminalNode.nodeTypeId = sdrNode->GetIdentifier(); - newTerminalNode.inputConnections = terminalNode.inputConnections; - newTerminalNode.parameters = terminalNode.parameters; + newTerminalNode.inputConnections = filterTask->terminalNode->inputConnections; + newTerminalNode.parameters = filterTask->terminalNode->parameters; + + // Replace the original filterTask->terminalNode with this newTerminalNode + filterTask->hdNetwork.nodes[filterTask->terminalNodePath] = newTerminalNode; + } + + return mxShader; +} - // Replace the original terminalNode with this newTerminalNode - hdNetwork->nodes[terminalNodePath] = newTerminalNode; +std::unique_ptr +HdSt_CreateMaterialXGeneratorTask( + SdfPath const& materialPath, + VtValue vtMat, + Hgi const& hgi) +{ + HD_TRACE_FUNCTION(); + + if (!vtMat.IsHolding()) { + return nullptr; + } + + HdMaterialNetworkMap const& hdNetworkMap = + vtMat.UncheckedGet(); + + if (hdNetworkMap.terminals.empty() || hdNetworkMap.map.empty()) { + return nullptr; + } + + bool isVolume = false; + auto filterTask = std::make_shared(); + filterTask->hdNetwork = + HdConvertToHdMaterialNetwork2(hdNetworkMap, &isVolume); + + if (isVolume) { + // We only use the early init mechanism for MaterialX surface shaders + return nullptr; + } + + filterTask->terminalNode = + HdSt_GetTerminalNode( + filterTask->hdNetwork, + HdMaterialTerminalTokens->surface, + &filterTask->terminalNodePath); + + if ( !filterTask->terminalNode + || !filterTask->IsMaterialX(&SdrRegistry::GetInstance())) { + return nullptr; } - return glslfxShader; + return std::make_unique( + filterTask, + materialPath, + hgi); } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/hdSt/materialXFilter.h b/pxr/imaging/hdSt/materialXFilter.h index d3647ba5d7..e9abc8781c 100644 --- a/pxr/imaging/hdSt/materialXFilter.h +++ b/pxr/imaging/hdSt/materialXFilter.h @@ -18,7 +18,16 @@ PXR_NAMESPACE_OPEN_SCOPE -// Storing MaterialX-Hydra counterparts and other Hydra specific information +class Hgi; +struct HdSt_MaterialFilterTask; + +using HdStResourceRegistrySharedPtr = + std::shared_ptr; + +using HdSt_MaterialFilterTaskSharedPtr = + std::shared_ptr; + +/// Storing MaterialX-Hydra counterparts and other Hydra-specific information struct HdSt_MxShaderGenInfo { HdSt_MxShaderGenInfo() : textureNames(MaterialX::StringVec()), @@ -35,18 +44,68 @@ struct HdSt_MxShaderGenInfo { bool bindlessTexturesEnabled; }; +/// Encapsulates the input data for MaterialX codegen for an individual shader. +/// +class HdSt_MaterialXGeneratorTask final +{ +public: + /// Modifies the input filter task - stores mappings between original and + /// anonymized Sdf paths. + /// + HdSt_MaterialXGeneratorTask( + HdSt_MaterialFilterTaskSharedPtr filterTask, + SdfPath const& materialPath, + Hgi const& hgi); + + /// The hash that uniquely identifies the shader to be generated. This is + /// based on the anonymized shader network. + size_t GetShaderHash() const + { + return _shaderHash; + } + + MaterialX::ShaderPtr Generate() const; + + HdSt_MaterialFilterTaskSharedPtr GetFilterTask() { + return _filterTask; + } + +private: + // To keep input data such as the material network alive for the duration + // of the codegen process + HdSt_MaterialFilterTaskSharedPtr _filterTask; + + HdMaterialNetwork2 _hdNetwork; + SdfPath _terminalNodePath; + + std::string const& _strMaterialTag; + + size_t _shaderHash = 0; + + HdMaterialNode2 const& _terminalNode; + SdfPath const _materialPath; + + TfToken const& _apiName; + bool const _bindlessTexturesEnabled; +}; + /// MaterialX Filter -/// Converting a MaterialX node to one with a generated MaterialX glslfx file +/// Converts a MaterialX node to one with a generated MaterialX shader HDST_API MaterialX::ShaderPtr HdSt_ApplyMaterialXFilter( - HdMaterialNetwork2* hdNetwork, + HdSt_MaterialFilterTaskSharedPtr filterTask, SdfPath const& materialPath, - HdMaterialNode2 const& terminalNode, - SdfPath const& terminalNodePath, HdSt_MaterialParamVector* materialParams, HdStResourceRegistry *resourceRegistry); -// Generates the glsfx shader for the given MaterialX Document +/// Create a MaterialX shader codegen task object for parallel execution +std::unique_ptr +HdSt_CreateMaterialXGeneratorTask( + SdfPath const& materialPath, + VtValue vtMat, + Hgi const& hgi); + +/// Generate the shader for the given MaterialX document HDST_API MaterialX::ShaderPtr HdSt_GenMaterialXShader( MaterialX::DocumentPtr const& mxDoc, diff --git a/pxr/imaging/hdSt/materialXSyncSceneIndex.cpp b/pxr/imaging/hdSt/materialXSyncSceneIndex.cpp new file mode 100644 index 0000000000..bab45bfabd --- /dev/null +++ b/pxr/imaging/hdSt/materialXSyncSceneIndex.cpp @@ -0,0 +1,241 @@ +// +// Copyright 2024 Pixar +// +// Licensed under the terms set forth in the LICENSE.txt file available at +// https://openusd.org/license. +// + +#include "pxr/imaging/hdSt/materialXSyncSceneIndex.h" +#include "pxr/imaging/hdSt/materialXFilter.h" +#include "pxr/imaging/hdSt/renderDelegate.h" +#include "pxr/imaging/hdSt/resourceRegistry.h" + +#include "pxr/imaging/hd/sceneIndexAdapterSceneDelegate.h" +#include "pxr/imaging/hd/tokens.h" + +#include "pxr/usd/sdf/path.h" + +namespace mx = MaterialX; + +PXR_NAMESPACE_OPEN_SCOPE + +HdSt_MaterialXSyncSceneIndexRefPtr +HdSt_MaterialXSyncSceneIndex::New( + const HdSceneIndexBaseRefPtr& inputSceneIndex, + const HdStRenderDelegate& renderDelegate) +{ + return TfCreateRefPtr( + new HdSt_MaterialXSyncSceneIndex(inputSceneIndex, renderDelegate)); +} + +HdSt_MaterialXSyncSceneIndex::HdSt_MaterialXSyncSceneIndex( + const HdSceneIndexBaseRefPtr& inputSceneIndex, + const HdStRenderDelegate& renderDelegate) + : HdSingleInputFilteringSceneIndexBase( inputSceneIndex) + , _renderDelegate( renderDelegate) +{ +} + +HdSt_MaterialXSyncSceneIndex::~HdSt_MaterialXSyncSceneIndex() +{ +} + +HdSceneIndexPrim +HdSt_MaterialXSyncSceneIndex::GetPrim(const SdfPath &primPath) const +{ + // Just forward to input scene index - we don't modify prim data + return _GetInputSceneIndex()->GetPrim(primPath); +} + +SdfPathVector +HdSt_MaterialXSyncSceneIndex::GetChildPrimPaths(const SdfPath &primPath) const +{ + // Just forward to input scene index - we don't modify hierarchy + return _GetInputSceneIndex()->GetChildPrimPaths(primPath); +} + +void +HdSt_MaterialXSyncSceneIndex::_PrimsAdded( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::AddedPrimEntries &entries) +{ + HD_TRACE_FUNCTION(); + + for (const HdSceneIndexObserver::AddedPrimEntry& entry : entries) { + if (entry.primType == HdPrimTypeTokens->material) { + _AddMaterial(entry.primPath); + } + } + + // Forward the notification to observers + _SendPrimsAdded(entries); +} + +void +HdSt_MaterialXSyncSceneIndex::_PrimsRemoved( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::RemovedPrimEntries &entries) +{ + // Just forward the notification - we don't need to do anything special for + // removed prims + _SendPrimsRemoved(entries); +} + +void +HdSt_MaterialXSyncSceneIndex::_PrimsDirtied( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::DirtiedPrimEntries &entries) +{ + HD_TRACE_FUNCTION(); + + for (const HdSceneIndexObserver::DirtiedPrimEntry& entry : entries) { + // If the dirtied prim is a material for which we've cached a filter + // task, remove the task since it's now stale. Any ongoing generator + // tasks will keep their own shared pointers to the respective filter + // tasks, so this is safe. If the element is not in the map, then the + // lookup won't involve locking. + _FilterTaskMap::accessor accessor; + if (_filterTaskMap.find(accessor, entry.primPath)) { + _filterTaskMap.erase(accessor); + } + } + + // Forward the notification to observers + _SendPrimsDirtied(entries); +} + +void +HdSt_MaterialXSyncSceneIndex::_AddMaterial(const SdfPath& id) +{ + HD_TRACE_FUNCTION(); + + HdSceneIndexPrim materialPrim = + _GetInputSceneIndex()->GetPrim(id); + + VtValue vtMat = HdSceneIndexAdapterSceneDelegate::GetMaterialResourceFromSceneIndexPrim( + materialPrim, + _renderDelegate.GetMaterialRenderContexts()); + + HdStResourceRegistrySharedPtr resourceRegistry = + std::static_pointer_cast( + _renderDelegate.GetResourceRegistry()); + + auto generatorTask = HdSt_CreateMaterialXGeneratorTask( + id, + vtMat, + *resourceRegistry->GetHgi()); + + if (generatorTask) { + { + _FilterTaskMap::accessor accessor; + _filterTaskMap.insert(accessor, id); + accessor->second = generatorTask->GetFilterTask(); + } + _AddGeneratorTask(std::move(generatorTask), resourceRegistry); + } +} + +void +HdSt_MaterialXSyncSceneIndex::_AddGeneratorTask( + std::unique_ptr generatorTask, + HdStResourceRegistrySharedPtr& resourceRegistry) +{ + HD_TRACE_FUNCTION(); + + if (resourceRegistry->ContainsMaterialXShader( + generatorTask->GetShaderHash())) { + // We already have a shader for this topology. + return; + } + + // Use a separate concurrent container for tracking unique generator tasks. + // The generated shader will be registered in the resource registry when + // the task completes. + auto insertResult = + _generatorTaskSet.insert(generatorTask->GetShaderHash()); + + if (insertResult.second) { + // If no generator task with the given hash existed, + // run the task + _LaunchGeneratorTask( + std::move(generatorTask), resourceRegistry); + } +} + +void +HdSt_MaterialXSyncSceneIndex::_LaunchGeneratorTask( + std::unique_ptr generatorTask, + HdStResourceRegistrySharedPtr& resourceRegistry) +{ + HD_TRACE_FUNCTION(); + + // Just in case, pass the registry to the parallel task by a weak pointer, + // to avoid keeping it alive if something goes wrong with synchronization. + std::weak_ptr resourceRegistryWeakPtr + = resourceRegistry; + + _dispatcher.Run( + [generatorTask = std::move(generatorTask), resourceRegistryWeakPtr] + { + HD_TRACE_FUNCTION(); + + mx::ShaderPtr mxShader; + + try { + mxShader = generatorTask->Generate(); + } + catch (mx::Exception& exception) { + TF_CODING_ERROR("Unable to generate the MaterialX shader.\n" + "MxException: %s", exception.what()); + } + + if (HdStResourceRegistrySharedPtr resourceRegistry = + resourceRegistryWeakPtr.lock()) { + + HdInstance mxShaderInstance = + resourceRegistry->RegisterMaterialXShader( + generatorTask->GetShaderHash()); + + // Store the mx::ShaderPtr + mxShaderInstance.SetValue(mxShader); + } + else { + TF_CODING_ERROR( + "HdStResourceRegistry destroyed before " + "the async task completes"); + } + } + ); +} + +void +HdSt_MaterialXSyncSceneIndex::_Wait() +{ + HD_TRACE_FUNCTION(); + + _dispatcher.Wait(); + _generatorTaskSet.clear(); +} + +HdSt_MaterialFilterTaskSharedPtr +HdSt_MaterialXSyncSceneIndex::WaitAndExtractFilterTask(const SdfPath& materialPath) +{ + HD_TRACE_FUNCTION(); + + // Make sure that all generator tasks have completed + _Wait(); + + // Find and extract the filter task from the map using thread-safe accessor + _FilterTaskMap::accessor accessor; + if (_filterTaskMap.find(accessor, materialPath)) { + // Transfer ownership by copying the shared_ptr and removing from map + HdSt_MaterialFilterTaskSharedPtr filterTask = accessor->second; + _filterTaskMap.erase(accessor); // Thread-safe erase using accessor + return filterTask; + } + + // No filter task found for this path + return nullptr; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/hdSt/materialXSyncSceneIndex.h b/pxr/imaging/hdSt/materialXSyncSceneIndex.h new file mode 100644 index 0000000000..36d691c550 --- /dev/null +++ b/pxr/imaging/hdSt/materialXSyncSceneIndex.h @@ -0,0 +1,117 @@ +// +// Copyright 2024 Pixar +// +// Licensed under the terms set forth in the LICENSE.txt file available at +// https://openusd.org/license. +// +#ifndef PXR_IMAGING_HD_ST_MATERIALX_SYNC_SCENE_INDEX_H +#define PXR_IMAGING_HD_ST_MATERIALX_SYNC_SCENE_INDEX_H + +#include "pxr/pxr.h" +#include "pxr/base/work/dispatcher.h" +#include "pxr/imaging/hd/filteringSceneIndex.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSt_MaterialXGeneratorTask; +struct HdSt_MaterialFilterTask; +class SdfPath; +class HdStRenderDelegate; + +using HdStResourceRegistrySharedPtr = + std::shared_ptr; + +using HdSt_MaterialFilterTaskSharedPtr = + std::shared_ptr; + +TF_DECLARE_REF_PTRS(HdSt_MaterialXSyncSceneIndex); + +/// Launches and manages parallel generator tasks, including caching them by +/// material hash, so that only one task is launched for each unique material. +class HdSt_MaterialXSyncSceneIndex + : public HdSingleInputFilteringSceneIndexBase +{ +public: + static HdSt_MaterialXSyncSceneIndexRefPtr New( + const HdSceneIndexBaseRefPtr& inputSceneIndex, + const HdStRenderDelegate& renderDelegate); + + ~HdSt_MaterialXSyncSceneIndex() override; + + /// Wait for codegen tasks to complete and look up the filter task in the + /// internal map by the material path. If found, remove the task from the + /// map and return it to the caller. If not found, return nullptr. + HdSt_MaterialFilterTaskSharedPtr + WaitAndExtractFilterTask(const SdfPath& materialPath); + + // HdSceneIndexBase overrides + HdSceneIndexPrim GetPrim(const SdfPath &primPath) const override; + SdfPathVector GetChildPrimPaths(const SdfPath &primPath) const override; + +protected: + // HdSingleInputFilteringSceneIndexBase overrides + void _PrimsAdded( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::AddedPrimEntries &entries) override; + + void _PrimsRemoved( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::RemovedPrimEntries &entries) override; + + void _PrimsDirtied( + const HdSceneIndexBase &sender, + const HdSceneIndexObserver::DirtiedPrimEntries &entries) override; + +private: + HdSt_MaterialXSyncSceneIndex( + const HdSceneIndexBaseRefPtr& inputSceneIndex, + const HdStRenderDelegate& renderDelegate); + + /// Wait for all parallel tasks to complete + void _Wait(); + + void _AddMaterial(const SdfPath& id); + + // Add the generator task to the cache. If this material hash hasn't been + // added before, the task is launched on a worker thread. + void _AddGeneratorTask( + std::unique_ptr generatorTask, + HdStResourceRegistrySharedPtr& resourceRegistry); + + // Lunch the generator task + void _LaunchGeneratorTask( + std::unique_ptr generatorTask, + HdStResourceRegistrySharedPtr& resourceRegistry); + + const HdStRenderDelegate& _renderDelegate; + + // Synchronizes the generator tasks + WorkDispatcher _dispatcher; + + // Thread-safe set of material hashes for which generator tasks have been + // launched. Used to prevent launching duplicate tasks. + using _GeneratorTaskSet = tbb::concurrent_unordered_set; + _GeneratorTaskSet _generatorTaskSet; + + struct _PathHashCompare { + static bool equal(const SdfPath &a, const SdfPath &b) { + return a == b; + } + static size_t hash(const SdfPath &path) { + return hash_value(path); + } + }; + + using _FilterTaskMap = tbb::concurrent_hash_map< + SdfPath, HdSt_MaterialFilterTaskSharedPtr, _PathHashCompare>; + + // Thread-safe map of filter tasks indexed by material path + _FilterTaskMap _filterTaskMap; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif //PXR_IMAGING_HD_ST_MATERIALX_SYNC_SCENE_INDEX_H diff --git a/pxr/imaging/hdSt/renderDelegate.cpp b/pxr/imaging/hdSt/renderDelegate.cpp index 60be88295e..2f4efca6d1 100644 --- a/pxr/imaging/hdSt/renderDelegate.cpp +++ b/pxr/imaging/hdSt/renderDelegate.cpp @@ -45,6 +45,10 @@ #include "pxr/base/tf/getenv.h" #include "pxr/base/tf/staticTokens.h" +#ifdef PXR_MATERIALX_SUPPORT_ENABLED +#include "pxr/imaging/hdSt/materialXSyncSceneIndex.h" +#endif + PXR_NAMESPACE_OPEN_SCOPE @@ -54,6 +58,10 @@ TF_DEFINE_ENV_SETTING(HD_ENABLE_GPU_TINY_PRIM_CULLING, false, TF_DEFINE_ENV_SETTING(HDST_MAX_LIGHTS, 16, "Maximum number of lights to render with"); +#ifdef PXR_MATERIALX_SUPPORT_ENABLED +TF_DEFINE_ENV_SETTING(HDST_ENABLE_PARALLEL_MTLX_CODEGEN, false, + "Enable early parallelized MaterialX codegen"); +#endif TF_DEFINE_ENV_SETTING(HDST_DOME_LIGHT_CUBEMAP_TARGET_MEMORY_MB, 0, "Maximum memory target in MB for the cubemap computed " "from the latlong texture for the dome light."); @@ -626,4 +634,24 @@ HdStRenderDelegate::_ApplyTextureSettings() HdStTextureType::Field, 1048576 * memInMb); } +#ifdef PXR_MATERIALX_SUPPORT_ENABLED +void +HdStRenderDelegate::SetTerminalSceneIndex( + const HdSceneIndexBaseRefPtr &terminalSceneIndex) +{ + if (!TfGetEnvSetting(HDST_ENABLE_PARALLEL_MTLX_CODEGEN)) { + return; + } + + _materialXSyncSceneIndex = + HdSt_MaterialXSyncSceneIndex::New(terminalSceneIndex, *this); +} + +HdSt_MaterialXSyncSceneIndex* +HdStRenderDelegate::GetMaterialXSyncSceneIndex() +{ + return get_pointer(_materialXSyncSceneIndex); +} +#endif + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/hdSt/renderDelegate.h b/pxr/imaging/hdSt/renderDelegate.h index f40371bc8a..a9e0cd5dec 100644 --- a/pxr/imaging/hdSt/renderDelegate.h +++ b/pxr/imaging/hdSt/renderDelegate.h @@ -24,6 +24,9 @@ using HdStDrawItemsCachePtr = HdSt_DrawItemsCache *; using HdStResourceRegistrySharedPtr = std::shared_ptr; +class HdSt_MaterialXSyncSceneIndex; +TF_DECLARE_REF_PTRS(HdSt_MaterialXSyncSceneIndex); + /// /// HdStRenderDelegate /// @@ -110,14 +113,24 @@ class HdStRenderDelegate final : public HdRenderDelegate HDST_API HdRenderSettingDescriptorList - GetRenderSettingDescriptors() const override; + GetRenderSettingDescriptors() const override; HDST_API VtDictionary GetRenderStats() const override; HDST_API HdAovDescriptor - GetDefaultAovDescriptor(TfToken const& name) const override; + GetDefaultAovDescriptor(TfToken const& name) const override; + +#ifdef PXR_MATERIALX_SUPPORT_ENABLED + HDST_API + void + SetTerminalSceneIndex( + const HdSceneIndexBaseRefPtr &terminalSceneIndex) override; + + HDST_API + HdSt_MaterialXSyncSceneIndex* GetMaterialXSyncSceneIndex(); +#endif /// Flag for the HdxTaskControllerSceneIndex. /// @@ -166,6 +179,10 @@ class HdStRenderDelegate final : public HdRenderDelegate std::unique_ptr _renderParam; HdStDrawItemsCacheUniquePtr _drawItemsCache; + +#ifdef PXR_MATERIALX_SUPPORT_ENABLED + HdSt_MaterialXSyncSceneIndexRefPtr _materialXSyncSceneIndex; +#endif }; diff --git a/pxr/imaging/hdSt/resourceRegistry.cpp b/pxr/imaging/hdSt/resourceRegistry.cpp index 29fb8c507f..336c20284d 100644 --- a/pxr/imaging/hdSt/resourceRegistry.cpp +++ b/pxr/imaging/hdSt/resourceRegistry.cpp @@ -666,6 +666,15 @@ HdStResourceRegistry::RegisterMaterialXShader( { return _materialXShaderRegistry.GetInstance(id); } + +bool +HdStResourceRegistry::ContainsMaterialXShader( + HdInstance::ID id) +{ + bool found = false; + _materialXShaderRegistry.FindInstance(id, &found); + return found; +} #endif HdInstance diff --git a/pxr/imaging/hdSt/resourceRegistry.h b/pxr/imaging/hdSt/resourceRegistry.h index d23b91ae8a..d79bd211b8 100644 --- a/pxr/imaging/hdSt/resourceRegistry.h +++ b/pxr/imaging/hdSt/resourceRegistry.h @@ -433,6 +433,11 @@ class HdStResourceRegistry final : public HdResourceRegistry HDST_API HdInstance RegisterMaterialXShader(HdInstance::ID id); + + /// Determine if a MaterialX shader with the given ID has been registered + HDST_API + bool + ContainsMaterialXShader(HdInstance::ID id); #endif /// Register a Hgi resource bindings into the registry.