diff --git a/pxr/imaging/plugin/hdRpr/CMakeLists.txt b/pxr/imaging/plugin/hdRpr/CMakeLists.txt index 9b5ddfeba..e4debe6c8 100644 --- a/pxr/imaging/plugin/hdRpr/CMakeLists.txt +++ b/pxr/imaging/plugin/hdRpr/CMakeLists.txt @@ -88,6 +88,11 @@ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/rif_models.version ${RIF_VERSION_STRING}) list(APPEND RIF_MODEL_RESOURCE_FILES "${CMAKE_CURRENT_BINARY_DIR}/rif_models.version${_sep}rif_models/rif_models.version") +if(HoudiniUSD_FOUND) + set(RESOURCE_WATCHER_H resourceWatcher.h) + set(RESOURCE_WATCHER_CPP resourceWatcher.cpp) +endif() + pxr_plugin(hdRpr DISABLE_PRECOMPILED_HEADERS @@ -152,6 +157,7 @@ pxr_plugin(hdRpr PRIVATE_HEADERS baseRprim.h api.h + ${RESOURCE_WATCHER_H} RESOURCE_FILES plugInfo.json @@ -163,6 +169,7 @@ pxr_plugin(hdRpr ${CMAKE_CURRENT_BINARY_DIR}/config.cpp ndrDiscoveryPlugin.cpp ndrParserPlugin.cpp + ${RESOURCE_WATCHER_CPP} ) if(RPR_EXR_EXPORT_ENABLED) diff --git a/pxr/imaging/plugin/hdRpr/renderDelegate.cpp b/pxr/imaging/plugin/hdRpr/renderDelegate.cpp index 8723a5f94..de077bdf0 100644 --- a/pxr/imaging/plugin/hdRpr/renderDelegate.cpp +++ b/pxr/imaging/plugin/hdRpr/renderDelegate.cpp @@ -45,6 +45,10 @@ limitations under the License. #include #include +#ifdef BUILD_AS_HOUDINI_PLUGIN +#include "resourceWatcher.h" +#endif + PXR_NAMESPACE_OPEN_SCOPE static HdRprApi* g_rprApi = nullptr; @@ -187,9 +191,15 @@ HdRprDelegate::HdRprDelegate(HdRenderSettingsMap const& renderSettings) { } m_lastCreatedInstance = this; +#ifdef BUILD_AS_HOUDINI_PLUGIN + InitWatcher(); +#endif } HdRprDelegate::~HdRprDelegate() { +#ifdef BUILD_AS_HOUDINI_PLUGIN + NotifyRenderFinished(); +#endif g_rprApi = nullptr; m_lastCreatedInstance = nullptr; } @@ -387,11 +397,17 @@ bool HdRprDelegate::IsPauseSupported() const { } bool HdRprDelegate::Pause() { +#ifdef BUILD_AS_HOUDINI_PLUGIN + NotifyRenderFinished(); +#endif m_renderThread.PauseRender(); return true; } bool HdRprDelegate::Resume() { +#ifdef BUILD_AS_HOUDINI_PLUGIN + NotifyRenderStarted(); +#endif m_renderThread.ResumeRender(); return true; } diff --git a/pxr/imaging/plugin/hdRpr/resourceWatcher.cpp b/pxr/imaging/plugin/hdRpr/resourceWatcher.cpp new file mode 100644 index 000000000..482f87c80 --- /dev/null +++ b/pxr/imaging/plugin/hdRpr/resourceWatcher.cpp @@ -0,0 +1,358 @@ +#include "resourceWatcher.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pxr/base/arch/env.h" +#include "pxr/imaging/rprUsd/config.h" +#include +namespace fs = ghc::filesystem; + +PXR_NAMESPACE_OPEN_SCOPE + +#ifdef GET_IS_BYPASSED +#error "GET_IS_BYPASSED is defined elsewhere" +#else +#define GET_IS_BYPASSED(classname) \ + {\ + classname* n = dynamic_cast(node); \ + if (n) { \ + hasBypassParam = true; \ + bypass = n->isBypassed(); \ + return; \ + } \ + } +#endif + +void GetBypassed(HOM_Node* node, bool& hasBypassParam, bool& bypass) { + GET_IS_BYPASSED(HOM_ChopNode) + GET_IS_BYPASSED(HOM_CopNode) + GET_IS_BYPASSED(HOM_DopNode) + GET_IS_BYPASSED(HOM_LopNode) + GET_IS_BYPASSED(HOM_RopNode) + GET_IS_BYPASSED(HOM_SopNode) + GET_IS_BYPASSED(HOM_TopNode) + GET_IS_BYPASSED(HOM_VopNode) + hasBypassParam = false; + bypass = false; +} + +#undef GET_IS_BYPASSED + +#ifdef SET_BYPASS +#error "SET_BYPASS is defined elsewhere" +#else +#define SET_BYPASS(classname) \ + {\ + classname* n = dynamic_cast(node); \ + if (n) { \ + n->bypass(bypass); \ + return; \ + } \ + } +#endif + +void SetBypassed(HOM_Node* node, bool bypass) { + SET_BYPASS(HOM_ChopNode) + SET_BYPASS(HOM_CopNode) + SET_BYPASS(HOM_DopNode) + SET_BYPASS(HOM_LopNode) + SET_BYPASS(HOM_RopNode) + SET_BYPASS(HOM_SopNode) + SET_BYPASS(HOM_TopNode) + SET_BYPASS(HOM_VopNode) +} + +#undef SET_BYPASS + +typedef std::map NodesToRestoreSet; + +bool updateLock = false; + +bool ResourceManagementActive(HOM_Module& hom) { + auto result = hom.hscript("if( $RPR_MEM_MANAGEMENT ) then\necho 1;\nelse\necho 0;\nendif"); + return result.size() > 0 && result[0].size() > 0 && result[0][0] == '1'; +} + +void ReadMemManagementFlag() { + HOM_Module& hom = HOM(); + RprUsdConfig* config; + auto configLock = RprUsdConfig::GetInstance(&config); + hom.hscript(config->GetMemManagement() ? "set -g RPR_MEM_MANAGEMENT = 1" : "set -g RPR_MEM_MANAGEMENT = 0"); + hom.hscript("varchange RPR_MEM_MANAGEMENT"); +} + +void WriteMemManagementFlag() { + HOM_Module& hom = HOM(); + RprUsdConfig* config; + auto configLock = RprUsdConfig::GetInstance(&config); + config->SetMemManagement(ResourceManagementActive(hom)); +} + +bool IsHoudiniInstance() { + static bool tested = false; + static bool isHoudiniInstance = false; + if (!tested) { + HOM_Module& hom = HOM(); + isHoudiniInstance = hom.applicationName().rfind("houdini", 0) == 0; + } + return isHoudiniInstance; +} + +void DeActivateScene(NodesToRestoreSet& nodesToRestore) { + HOM_Module& hom = HOM(); + if (nodesToRestore.size() != 0 // already deactivated + || !IsHoudiniInstance() || !ResourceManagementActive(hom)) + { + return; + } + + HOM_Node* root = hom.root(); + auto children = root->children(); + for (HOM_ElemPtr& c : children) { + if (c.myPointer->name() == "stage") { + auto schildren = c.myPointer->children(); + for (HOM_ElemPtr& sc : schildren) { + bool hasBypassParam; + bool bypass; + GetBypassed(sc.myPointer, hasBypassParam, bypass); + if (hasBypassParam) { + nodesToRestore.emplace(sc.myPointer->name(), bypass); + SetBypassed(sc.myPointer, true); + } + } + } + } + + HOM_ui& ui = hom.ui(); + HOM_SceneViewer* sceneViewer = dynamic_cast(ui.paneTabOfType(HOM_paneTabType::SceneViewer)); + if (sceneViewer) { + sceneViewer->restartRenderer(); + } +} + +void ActivateScene(NodesToRestoreSet& nodesToRestore) { + if (!IsHoudiniInstance()) + { + return; + } + + HOM_Module& hom = HOM(); + HOM_Node* root = hom.root(); + auto children = root->children(); + for (HOM_ElemPtr& c : children) { + if (c.myPointer->name() == "stage") { + auto schildren = c.myPointer->children(); + for (HOM_ElemPtr& sc : schildren) { + auto it = nodesToRestore.find(sc.myPointer->name()); + if (it != nodesToRestore.end()) { + SetBypassed(sc.myPointer, (*it).second); + } + } + } + } + nodesToRestore.clear(); +} + +enum class MessageType { Started, Finished, Live }; + +struct MessageData { + hboost::interprocess::ipcdetail::OS_process_id_t pid; + MessageType messageType; +}; + +struct InterprocessMessage +{ + InterprocessMessage() : messageIn(false) {} + hboost::interprocess::interprocess_mutex mutex; + hboost::interprocess::interprocess_condition condEmpty; + hboost::interprocess::interprocess_condition condFull; + MessageData content; + bool messageIn; +}; + +void Notify(InterprocessMessage* message, bool started); + +class ResourceWatcher { +public: + ResourceWatcher(): m_shm(hboost::interprocess::open_or_create, "RprResourceWatcher", hboost::interprocess::read_write), m_message(nullptr) {} + + bool Init() { + try { + ReadMemManagementFlag(); + m_shm.truncate(sizeof(InterprocessMessage)); + m_region = std::make_unique(m_shm, hboost::interprocess::read_write); + void* addr = m_region->get_address(); + m_message = new (addr) InterprocessMessage; + } + catch (hboost::interprocess::interprocess_exception& ex) { + std::cout << "Resource watcher failure: " << ex.what() << std::endl; + return false; + } + return true; + } + + InterprocessMessage* GetInterprocMessage() { return m_message; } +private: + hboost::interprocess::shared_memory_object m_shm; + std::unique_ptr m_region; + InterprocessMessage* m_message; +}; + +static ResourceWatcher resourceWatcher; +static std::thread* listenerThread = nullptr; +static std::thread* checkAliveThread = nullptr; +std::mutex timePointsLock; +std::map timePoints; +NodesToRestoreSet nodesToRestore; + +void Listen(InterprocessMessage* message) +{ + try { + do { + hboost::interprocess::scoped_lock lock(message->mutex); + if (!message->messageIn) { + message->condEmpty.wait(lock); + } + else { + if (message->content.pid != hboost::interprocess::ipcdetail::get_current_process_id()) { // Ignore messages from the same process + if (message->content.messageType == MessageType::Started) { + std::lock_guard lock(timePointsLock); + timePoints[message->content.pid] = std::chrono::steady_clock::now(); + updateLock = true; + fprintf(stdout, "Deactivate %d\n", message->content.pid); + DeActivateScene(nodesToRestore); + updateLock = false; + } + else if (message->content.messageType == MessageType::Finished) { + std::lock_guard lock(timePointsLock); + timePoints.erase(message->content.pid); + updateLock = true; + ActivateScene(nodesToRestore); + updateLock = false; + } + else if (message->content.messageType == MessageType::Live) { + fprintf(stdout, "Live %d\n", message->content.pid); + std::lock_guard lock(timePointsLock); + timePoints[message->content.pid] = std::chrono::steady_clock::now(); + } + } + + message->messageIn = false; + message->condFull.notify_all(); + } + } while (true); + } + catch (hboost::interprocess::interprocess_exception& ex) { + std::cout << "Resource watcher failure: " << ex.what() << std::endl; + } +} + +void NotifyLive(InterprocessMessage* message) { + try { + auto pid = hboost::interprocess::ipcdetail::get_current_process_id(); + while (true) { + { // code block where the mutex is locked + hboost::interprocess::scoped_lock lock(message->mutex); + if (message->messageIn) { + message->condFull.wait(lock); + } + message->content.pid = pid; + message->content.messageType = MessageType::Live; + message->condEmpty.notify_all(); + message->messageIn = true; + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + } + catch (hboost::interprocess::interprocess_exception& ex) { + std::cout << ex.what() << std::endl; + } +} + +void CheckLive(InterprocessMessage* message) { + while (true) { + { // code block where the mutex is locked + std::lock_guard lock(timePointsLock); + bool anyAlive = false; + for (auto it = timePoints.begin(); it != timePoints.end(); ++it) { + auto interval = std::chrono::steady_clock::now().time_since_epoch() - (*it).second.time_since_epoch(); + double seconds = (double)interval.count() / 1000000000.0; + int maxTimeoutSeconds = 5; + if (seconds < maxTimeoutSeconds) { + anyAlive = true; + break; + } + } + if (!anyAlive) { + timePoints.clear(); + if (nodesToRestore.size() != 0) { + ActivateScene(nodesToRestore); + } + } + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} + +void Notify(InterprocessMessage* message, bool started) { + if (updateLock) { + return; + } + try { + hboost::interprocess::scoped_lock lock(message->mutex); + if (message->messageIn) { + message->condFull.wait(lock); + } + message->content.pid = hboost::interprocess::ipcdetail::get_current_process_id(); + message->content.messageType = started ? MessageType::Started : MessageType::Finished; + message->condEmpty.notify_all(); + message->messageIn = true; + } + catch (hboost::interprocess::interprocess_exception& ex) { + std::cout << ex.what() << std::endl; + } +} + +void InitWatcher() { + if (!checkAliveThread) { + resourceWatcher.Init(); + checkAliveThread = new std::thread(IsHoudiniInstance() ? CheckLive : NotifyLive, resourceWatcher.GetInterprocMessage()); + } + if (!listenerThread) { + listenerThread = new std::thread(Listen, resourceWatcher.GetInterprocMessage()); + } +} + +void NotifyRenderStarted() { + Notify(resourceWatcher.GetInterprocMessage(), true); +} + +void NotifyRenderFinished() { + WriteMemManagementFlag(); // calls on render delegate destructor, just as needed + Notify(resourceWatcher.GetInterprocMessage(), false); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/imaging/plugin/hdRpr/resourceWatcher.h b/pxr/imaging/plugin/hdRpr/resourceWatcher.h new file mode 100644 index 000000000..b5704211b --- /dev/null +++ b/pxr/imaging/plugin/hdRpr/resourceWatcher.h @@ -0,0 +1,27 @@ +/************************************************************************ +Copyright 2023 Advanced Micro Devices, Inc +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +************************************************************************/ + +#ifndef RESOURCE_WATCHER_H +#define RESOURCE_WATCHER_H + +#include "pxr/imaging/rprUsd/api.h" + +PXR_NAMESPACE_OPEN_SCOPE + +void InitWatcher(); +void NotifyRenderStarted(); +void NotifyRenderFinished(); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // RESOURCE_WATCHER_H \ No newline at end of file diff --git a/pxr/imaging/plugin/hdRpr/rprApi.cpp b/pxr/imaging/plugin/hdRpr/rprApi.cpp index cdea31975..28726ccec 100644 --- a/pxr/imaging/plugin/hdRpr/rprApi.cpp +++ b/pxr/imaging/plugin/hdRpr/rprApi.cpp @@ -58,6 +58,7 @@ using json = nlohmann::json; #include "pxr/base/tf/envSetting.h" #include "notify/message.h" +#include "resourceWatcher.h" #include #include @@ -3481,6 +3482,9 @@ Don't show this message again? } void Render(HdRprRenderThread* renderThread) { +#ifdef BUILD_AS_HOUDINI_PLUGIN + NotifyRenderStarted(); +#endif updateSyncTime(); m_startTime = std::chrono::high_resolution_clock::now(); RenderFrame(renderThread); diff --git a/pxr/imaging/plugin/rprHoudini/ui/MainMenuCommon.xml b/pxr/imaging/plugin/rprHoudini/ui/MainMenuCommon.xml index 56940adfb..46a2c7569 100644 --- a/pxr/imaging/plugin/rprHoudini/ui/MainMenuCommon.xml +++ b/pxr/imaging/plugin/rprHoudini/ui/MainMenuCommon.xml @@ -50,6 +50,21 @@ + + + RPR_MEM_MANAGEMENT + + + diff --git a/pxr/imaging/rprUsd/config.cpp b/pxr/imaging/rprUsd/config.cpp index 31664a05a..a74947f1a 100644 --- a/pxr/imaging/rprUsd/config.cpp +++ b/pxr/imaging/rprUsd/config.cpp @@ -153,6 +153,7 @@ bool GetJsonProperty(const char* propertyName, json const& json, T* property) { const char* kShowRestartRequiredMessage = "ShowRestartRequiredMessage"; const char* kTextureCacheDir = "TextureCacheDir"; const char* kKernelCacheDir = "KernelCacheDir"; +const char* kMemManagement = "MemManagement"; } // namespace anonymous @@ -235,6 +236,7 @@ void RprUsdConfig::SetKernelCacheDir(std::string const& newValue) { Save(); } } + std::string RprUsdConfig::GetKernelCacheDir() const { std::string ret; if (!GetJsonProperty(kKernelCacheDir, m_impl->cfg, &ret)) { @@ -251,6 +253,19 @@ std::string RprUsdConfig::GetPrecompiledKernelDir() const { return kernelsDir; } +bool RprUsdConfig::GetMemManagement() const { + bool ret = false; + GetJsonProperty(kMemManagement, m_impl->cfg, &ret); + return ret; +} + +void RprUsdConfig::SetMemManagement(bool newValue) { + if (m_impl->cfg[kMemManagement] != newValue) { + m_impl->cfg[kMemManagement] = newValue; + Save(); + } +} + std::string RprUsdConfig::GetDeviceConfigurationFilepath() const { std::string configCacheDir = GetDefaultCacheDir("config"); return configCacheDir + ARCH_PATH_SEP + "devicesConfig.txt"; diff --git a/pxr/imaging/rprUsd/config.h b/pxr/imaging/rprUsd/config.h index 9e0b91009..db2048393 100644 --- a/pxr/imaging/rprUsd/config.h +++ b/pxr/imaging/rprUsd/config.h @@ -50,6 +50,11 @@ class RprUsdConfig { RPRUSD_API std::string GetPrecompiledKernelDir() const; + RPRUSD_API + bool GetMemManagement() const; + RPRUSD_API + void SetMemManagement(bool); + RPRUSD_API std::string GetDeviceConfigurationFilepath() const;