From 416775b2a7510a3917431bfda42eb619e52b7d11 Mon Sep 17 00:00:00 2001 From: Pal Mezei Date: Tue, 20 Apr 2021 21:32:05 +1000 Subject: [PATCH] Adding support for Delegate Render Products and turning off progressive mode when used in husk. (#758) Fixes #650 Fixes #755 --- common/constant_strings.h | 5 + render_delegate/render_delegate.cpp | 159 +++++++++++++-- render_delegate/render_delegate.h | 59 +++++- render_delegate/render_pass.cpp | 306 +++++++++++++++++++++------- render_delegate/render_pass.h | 21 ++ render_delegate/renderer_plugin.cpp | 17 +- 6 files changed, 469 insertions(+), 98 deletions(-) diff --git a/common/constant_strings.h b/common/constant_strings.h index e6c862ba71..36413b6c1a 100644 --- a/common/constant_strings.h +++ b/common/constant_strings.h @@ -168,6 +168,7 @@ ASTR(disk_light); ASTR(disp_map); ASTR(displacement); ASTR(distant_light); +ASTR(driver_deepexr); ASTR(emission); ASTR(emission_color); ASTR(emissiveColor); @@ -196,6 +197,7 @@ ASTR(frame); ASTR(gaussian_filter); ASTR(ginstance); ASTR(grids); +ASTR(husk); ASTR(hydra); ASTR(id); ASTR(id_pointer); @@ -232,6 +234,9 @@ ASTR(interactive_target_fps); ASTR(interactive_target_fps_min); ASTR(ior); ASTR(latlong); +ASTR(layer_enable_filtering); +ASTR(layer_half_precision); +ASTR(layer_tolerance); ASTR(light_group); ASTR(light_path_expressions); ASTR(linear); diff --git a/render_delegate/render_delegate.cpp b/render_delegate/render_delegate.cpp index 3cb479eef3..360f6e9307 100644 --- a/render_delegate/render_delegate.cpp +++ b/render_delegate/render_delegate.cpp @@ -64,6 +64,20 @@ TF_DEFINE_PRIVATE_TOKENS(_tokens, (openvdbAsset) ((arnoldGlobal, "arnold:global:")) (percentDone) + (delegateRenderProducts) + (orderedVars) + ((aovSettings, "aovDescriptor.aovSettings")) + (productType) + (productName) + (sourceType) + (sourceName) + (dataType) + ((format, "aovDescriptor.format")) + ((clearValue, "aovDescriptor.clearValue")) + ((multiSampled, "aovDescriptor.multiSampled")) + ((aovName, "driver:parameters:aov:name")) + (deep) + (raw) (instantaneousShutter) ); // clang-format on @@ -319,13 +333,15 @@ std::mutex HdArnoldRenderDelegate::_mutexResourceRegistry; std::atomic_int HdArnoldRenderDelegate::_counterResourceRegistry; HdResourceRegistrySharedPtr HdArnoldRenderDelegate::_resourceRegistry; -HdArnoldRenderDelegate::HdArnoldRenderDelegate() +HdArnoldRenderDelegate::HdArnoldRenderDelegate(HdArnoldRenderContext context) : _context(context) { _lightLinkingChanged.store(false, std::memory_order_release); _id = SdfPath(TfToken(TfStringPrintf("/HdArnoldRenderDelegate_%p", this))); if (AiUniverseIsActive()) { TF_CODING_ERROR("There is already an active Arnold universe!"); } + // TODO(pal): We need to investigate if it's safe to set session to AI_SESSION_BATCH when rendering in husk for + // example. ie. is husk creating a separate render delegate for each frame, or syncs the changes? AiBegin(AI_SESSION_INTERACTIVE); _supportedRprimTypes = { HdPrimTypeTokens->mesh, HdPrimTypeTokens->volume, HdPrimTypeTokens->points, HdPrimTypeTokens->basisCurves}; @@ -354,7 +370,7 @@ HdArnoldRenderDelegate::HdArnoldRenderDelegate() _nativeRprimParams.emplace(AiNodeEntryGetNameAtString(nodeEntry), std::move(paramList)); AiParamIteratorDestroy(paramIter); } - AiRenderSetHintStr(str::render_context, str::hydra); + AiRenderSetHintStr(str::render_context, _context == HdArnoldRenderContext::Hydra ? str::hydra : str::husk); std::lock_guard guard(_mutexResourceRegistry); if (_counterResourceRegistry.fetch_add(1) == 0) { _resourceRegistry.reset(new HdResourceRegistry()); @@ -394,9 +410,13 @@ HdArnoldRenderDelegate::HdArnoldRenderDelegate() _renderParam.reset(new HdArnoldRenderParam()); - // AiRenderSetHintBool(str::progressive, true); // We need access to both beauty and P at the same time. - AiRenderSetHintBool(str::progressive_show_all_outputs, true); + if (_context == HdArnoldRenderContext::Husk) { + AiRenderSetHintBool(str::progressive, false); + AiNodeSetBool(_options, str::enable_progressive_render, false); + } else { + AiRenderSetHintBool(str::progressive_show_all_outputs, true); + } } HdArnoldRenderDelegate::~HdArnoldRenderDelegate() @@ -423,6 +443,11 @@ const TfTokenVector& HdArnoldRenderDelegate::GetSupportedBprimTypes() const { re void HdArnoldRenderDelegate::_SetRenderSetting(const TfToken& _key, const VtValue& _value) { + // Special setting that describes custom output, like deep AOVs. + if (_key == _tokens->delegateRenderProducts) { + _ParseDelegateRenderProducts(_value); + return; + } TfToken key; _RemoveArnoldGlobalPrefix(_key, key); @@ -448,23 +473,33 @@ void HdArnoldRenderDelegate::_SetRenderSetting(const TfToken& _key, const VtValu AiMsgSetLogFileName(_logFile.c_str()); } } else if (key == str::t_enable_progressive_render) { - _CheckForBoolValue(value, [&](const bool b) { - AiRenderSetHintBool(str::progressive, b); - AiNodeSetBool(_options, str::enable_progressive_render, b); - }); + if (_context != HdArnoldRenderContext::Husk) { + _CheckForBoolValue(value, [&](const bool b) { + AiRenderSetHintBool(str::progressive, b); + AiNodeSetBool(_options, str::enable_progressive_render, b); + }); + } } else if (key == str::t_progressive_min_AA_samples) { - _CheckForIntValue(value, [&](const int i) { AiRenderSetHintInt(str::progressive_min_AA_samples, i); }); + if (_context != HdArnoldRenderContext::Husk) { + _CheckForIntValue(value, [&](const int i) { AiRenderSetHintInt(str::progressive_min_AA_samples, i); }); + } } else if (key == str::t_interactive_target_fps) { - if (value.IsHolding()) { - AiRenderSetHintFlt(str::interactive_target_fps, value.UncheckedGet()); + if (_context != HdArnoldRenderContext::Husk) { + if (value.IsHolding()) { + AiRenderSetHintFlt(str::interactive_target_fps, value.UncheckedGet()); + } } } else if (key == str::t_interactive_target_fps_min) { - if (value.IsHolding()) { - AiRenderSetHintFlt(str::interactive_target_fps_min, value.UncheckedGet()); + if (_context != HdArnoldRenderContext::Husk) { + if (value.IsHolding()) { + AiRenderSetHintFlt(str::interactive_target_fps_min, value.UncheckedGet()); + } } } else if (key == str::t_interactive_fps_min) { - if (value.IsHolding()) { - AiRenderSetHintFlt(str::interactive_fps_min, value.UncheckedGet()); + if (_context != HdArnoldRenderContext::Husk) { + if (value.IsHolding()) { + AiRenderSetHintFlt(str::interactive_fps_min, value.UncheckedGet()); + } } } else if (key == str::t_profile_file) { if (value.IsHolding()) { @@ -483,6 +518,100 @@ void HdArnoldRenderDelegate::_SetRenderSetting(const TfToken& _key, const VtValu } } +void HdArnoldRenderDelegate::_ParseDelegateRenderProducts(const VtValue& value) +{ + // Details about the data layout can be found here: + // https://www.sidefx.com/docs/hdk/_h_d_k__u_s_d_hydra.html#HDK_USDHydraHuskDRP + // Delegate Render Products are used by husk, so we only have to parse them once. + // We don't support cases where delegate render products are passed AFTER the first execution + // of the render pass. + if (!_delegateRenderProducts.empty()) { + return; + } + using DataType = VtArray; + if (!value.IsHolding()) { + return; + } + auto products = value.UncheckedGet(); + for (auto& productIter : products) { + HdArnoldDelegateRenderProduct product; + const auto* productType = TfMapLookupPtr(productIter, _tokens->productType); + // We only care about deep products for now. + if (productType == nullptr || !productType->IsHolding() || + productType->UncheckedGet() != _tokens->deep) { + continue; + } + // Ignoring cases where productName is not set. + const auto* productName = TfMapLookupPtr(productIter, _tokens->productName); + if (productName == nullptr || !productName->IsHolding()) { + continue; + } + product.productName = productName->UncheckedGet(); + productIter.erase(_tokens->productType); + productIter.erase(_tokens->productName); + // Elements of the HdAovSettingsMap in the product are either a list of RenderVars or generic attributes + // of the render product. + for (const auto& productElem : productIter) { + // If the key is "aovDescriptor.aovSettings" then we got the list of RenderVars. + if (productElem.first == _tokens->orderedVars) { + if (!productElem.second.IsHolding()) { + continue; + } + const auto& renderVars = productElem.second.UncheckedGet(); + for (const auto& renderVarIter : renderVars) { + HdArnoldRenderVar renderVar; + renderVar.sourceType = _tokens->raw; + // Each element either contains a setting, or "aovDescriptor.aovSettings" which will hold + // extra settings for the RenderVar including metadata. + for (const auto& renderVarElem : renderVarIter) { + if (renderVarElem.first == _tokens->aovSettings) { + if (!renderVarElem.second.IsHolding()) { + continue; + } + renderVar.settings = renderVarElem.second.UncheckedGet(); + // name is not coming through as a top parameter. + const auto* aovName = TfMapLookupPtr(renderVar.settings, _tokens->aovName); + if (aovName != nullptr) { + if (aovName->IsHolding()) { + renderVar.name = aovName->UncheckedGet(); + } else if (aovName->IsHolding()) { + renderVar.name = aovName->UncheckedGet().GetString(); + } + } + } else if ( + renderVarElem.first == _tokens->sourceName && + renderVarElem.second.IsHolding()) { + renderVar.sourceName = renderVarElem.second.UncheckedGet(); + } else if ( + renderVarElem.first == _tokens->sourceType && renderVarElem.second.IsHolding()) { + renderVar.sourceType = renderVarElem.second.UncheckedGet(); + } else if ( + renderVarElem.first == _tokens->dataType && renderVarElem.second.IsHolding()) { + renderVar.dataType = renderVarElem.second.UncheckedGet(); + } else if ( + renderVarElem.first == _tokens->format && renderVarElem.second.IsHolding()) { + renderVar.format = renderVarElem.second.UncheckedGet(); + } else if (renderVarElem.first == _tokens->clearValue) { + renderVar.clearValue = renderVarElem.second; + } else if ( + renderVarElem.first == _tokens->multiSampled && renderVarElem.second.IsHolding()) { + renderVar.multiSampled = renderVarElem.second.UncheckedGet(); + } + } + // Any other cases should have good/reasonable defaults. + if (!renderVar.sourceName.empty() && !renderVar.name.empty()) { + product.renderVars.emplace_back(std::move(renderVar)); + } + } + } else { + // It's a setting describing the RenderProduct. + product.settings.insert({productElem.first, productElem.second}); + } + } + _delegateRenderProducts.emplace_back(std::move(product)); + } +} + void HdArnoldRenderDelegate::SetRenderSetting(const TfToken& key, const VtValue& value) { _renderParam->Interrupt(); diff --git a/render_delegate/render_delegate.h b/render_delegate/render_delegate.h index c76272ce43..6d44541e96 100755 --- a/render_delegate/render_delegate.h +++ b/render_delegate/render_delegate.h @@ -49,11 +49,46 @@ PXR_NAMESPACE_OPEN_SCOPE +struct HdArnoldRenderVar { + /// Settings for the RenderVar. + HdAovSettingsMap settings; + /// Name of the render var. + std::string name; + /// Source name of the Render Var. + std::string sourceName; + /// Source type of the Render Var. + TfToken sourceType; + /// Data Type of the Render Var. + TfToken dataType; + /// Format of the AOV descriptor. + HdFormat format = HdFormatFloat32Vec4; + /// Clear Value, currently ignored. + VtValue clearValue; + /// Whether or not the render var is multisampled, currently ignored. + bool multiSampled = true; +}; + +struct HdArnoldDelegateRenderProduct { + /// List of RenderVars used by the RenderProduct. + std::vector renderVars; + /// Map of settings for the RenderProduct. + HdAovSettingsMap settings; + /// Name of the product, this is equal to the output location. + TfToken productName; +}; + +/// Render context for the render delegate. +enum class HdArnoldRenderContext { + Hydra, ///< Generic Hydra renderer. + Husk, ///< Husk from Houdini. +}; + /// Main class point for the Arnold Render Delegate. class HdArnoldRenderDelegate final : public HdRenderDelegate { public: HDARNOLD_API - HdArnoldRenderDelegate(); ///< Constructor for the Render Delegate. + HdArnoldRenderDelegate( + HdArnoldRenderContext context = HdArnoldRenderContext::Hydra); ///< Constructor for the Render Delegate. HDARNOLD_API ~HdArnoldRenderDelegate() override; ///< Destuctor for the Render Delegate. /// Returns an instance of HdArnoldRenderParam. @@ -298,6 +333,11 @@ class HdArnoldRenderDelegate final : public HdRenderDelegate { HDARNOLD_API bool ShouldSkipIteration(HdRenderIndex* renderIndex, float shutterOpen, float shutterClose); + using DelegateRenderProducts = std::vector; + /// Returns the list of available Delegate Render Products. + /// + /// @return Const Reference to the list of Delegate Render Products. + const DelegateRenderProducts& GetDelegateRenderProducts() const { return _delegateRenderProducts; } /// Advertise whether this delegate supports pausing and resuming of /// background render threads. Default implementation returns false. /// @@ -399,13 +439,14 @@ class HdArnoldRenderDelegate final : public HdRenderDelegate { ShapeMaterialChangesQueue _shapeMaterialUntrackQueue; ///< Queue to untrack shape material assignment changes. MaterialToShapeMap _materialToShapeMap; ///< Map to track dependencies between materials and shapes. - std::mutex _lightLinkingMutex; ///< Mutex to lock all light linking operations. - LightLinkingMap _lightLinks; ///< Light Link categories. - LightLinkingMap _shadowLinks; ///< Shadow Link categories. - std::atomic _lightLinkingChanged; ///< Whether or not Light Linking have changed. - TfTokenVector _supportedRprimTypes; ///< List of supported rprim types. - NativeRprimTypeMap _nativeRprimTypes; ///< Remapping between the native rprim type names and arnold types. - NativeRprimParams _nativeRprimParams; ///< List of parameters for native rprims. + std::mutex _lightLinkingMutex; ///< Mutex to lock all light linking operations. + LightLinkingMap _lightLinks; ///< Light Link categories. + LightLinkingMap _shadowLinks; ///< Shadow Link categories. + std::atomic _lightLinkingChanged; ///< Whether or not Light Linking have changed. + DelegateRenderProducts _delegateRenderProducts; ///< Delegate Render Products for batch renders via husk. + TfTokenVector _supportedRprimTypes; ///< List of supported rprim types. + NativeRprimTypeMap _nativeRprimTypes; ///< Remapping between the native rprim type names and arnold types. + NativeRprimParams _nativeRprimParams; ///< List of parameters for native rprims. /// Pointer to an instance of HdArnoldRenderParam. /// /// This is shared with all the primitives, so they can control the flow of @@ -417,6 +458,8 @@ class HdArnoldRenderDelegate final : public HdRenderDelegate { AtNode* _fallbackShader; ///< Pointer to the fallback Arnold Shader. AtNode* _fallbackVolumeShader; ///< Pointer to the fallback Arnold Volume Shader. std::string _logFile; + /// Top level render context using Hydra. Ie. Hydra, Solaris, Husk. + HdArnoldRenderContext _context = HdArnoldRenderContext::Hydra; int _verbosityLogFlags = AI_LOG_WARNINGS | AI_LOG_ERRORS; float _shutterOpen = 0.0f; ///< Saved Shutter Open value of the active camera. float _shutterClose = 0.0f; ///< Saved Shutter Close value of the active camera. diff --git a/render_delegate/render_pass.cpp b/render_delegate/render_pass.cpp index 46c5c9c8b2..f78fc4ddd9 100644 --- a/render_delegate/render_pass.cpp +++ b/render_delegate/render_pass.cpp @@ -36,9 +36,10 @@ #include +#include + #include "camera.h" #include "config.h" -#include #include "utils.h" PXR_NAMESPACE_OPEN_SCOPE @@ -50,6 +51,9 @@ TF_DEFINE_PRIVATE_TOKENS(_tokens, ((aovSetting, "arnold:")) ((aovSettingFilter, "arnold:filter")) ((aovSettingFormat, "driver:parameters:aov:format")) + ((tolerance, "arnold:layer_tolerance")) + ((enableFiltering, "arnold:layer_enable_filtering")) + ((halfPrecision, "arnold:layer_half_precision")) (sourceName) (sourceType) (dataType) @@ -199,13 +203,8 @@ const ArnoldAOVType& _GetArnoldAOVTypeFromTokenType(const TfToken& type) return iter == ArnoldAOVTypeMap.end() ? AOVTypeRGB : *iter->second; } -const TfToken& _GetTokenFromRenderBufferType(const HdRenderBuffer* buffer) +const TfToken _GetTokenFromHdFormat(HdFormat format) { - // Use a wide type to make sure all components are set. - if (Ai_unlikely(buffer == nullptr)) { - return _tokens->color4f; - } - const auto format = buffer->GetFormat(); switch (format) { case HdFormatUNorm8: return _tokens->uint8; @@ -253,6 +252,15 @@ const TfToken& _GetTokenFromRenderBufferType(const HdRenderBuffer* buffer) } } +const TfToken& _GetTokenFromRenderBufferType(const HdRenderBuffer* buffer) +{ + // Use a wide type to make sure all components are set. + if (Ai_unlikely(buffer == nullptr)) { + return _tokens->color4f; + } + return _GetTokenFromHdFormat(buffer->GetFormat()); +} + GfRect2i _GetDataWindow(const HdRenderPassStateSharedPtr& renderPassState) { #if PXR_VERSION >= 2102 @@ -270,6 +278,88 @@ GfRect2i _GetDataWindow(const HdRenderPassStateSharedPtr& renderPassState) #endif } +void _ReadNodeParameters(AtNode* node, const TfToken& prefix, const HdAovSettingsMap& settings) +{ + const AtNodeEntry* nodeEntry = AiNodeGetNodeEntry(node); + for (const auto& setting : settings) { + if (TfStringStartsWith(setting.first, prefix)) { + const AtString parameterName(setting.first.GetText() + prefix.size()); + // name is special in arnold + if (parameterName == str::name) { + continue; + } + const auto* paramEntry = AiNodeEntryLookUpParameter(nodeEntry, parameterName); + if (paramEntry != nullptr) { + HdArnoldSetParameter(node, paramEntry, setting.second); + } + } + } +}; + +AtNode* _CreateFilter(HdArnoldRenderDelegate* renderDelegate, const HdAovSettingsMap& aovSettings) +{ + // We need to make sure that it's holding a string, then try to create it to make sure + // it's a node type supported by Arnold. + const auto filterType = _GetOptionalSetting(aovSettings, _tokens->aovSettingFilter, std::string{}); + if (filterType.empty()) { + return nullptr; + } + AtNode* filter = AiNode(renderDelegate->GetUniverse(), filterType.c_str()); + if (filter == nullptr) { + return filter; + } + const auto filterNameStr = + renderDelegate->GetLocalNodeName(AtString{TfStringPrintf("HdArnoldRenderPass_filter_%p", filter).c_str()}); + AiNodeSetStr(filter, str::name, filterNameStr); + const auto* nodeEntry = AiNodeGetNodeEntry(filter); + // We are first checking for the filter parameters prefixed with "arnold:", then doing a second + // loop to check for "arnold:filter_type:" prefixed parameters. The reason for two loops is + // we want the second version to overwrite the first one, and with unordered_map, we are not + // getting any sort of ordering. + _ReadNodeParameters(filter, _tokens->aovSetting, aovSettings); + _ReadNodeParameters( + filter, TfToken{TfStringPrintf("%s%s:", _tokens->aovSetting.GetText(), filterType.c_str())}, aovSettings); + return filter; +} + +const std::string _CreateAOV( + HdArnoldRenderDelegate* renderDelegate, const ArnoldAOVType& arnoldTypes, const std::string& name, + const TfToken& sourceType, const std::string& sourceName, AtNode*& writer, AtNode*& reader, + std::vector& lightPathExpressions, std::vector& aovShaders) +{ + if (sourceType == _tokens->lpe) { + // We have to add the light path expression to the outputs node in the format of: + // "aov_name lpe" like "beauty C.*" + lightPathExpressions.emplace_back(TfStringPrintf("%s %s", name.c_str(), sourceName.c_str()).c_str()); + return name; + } else if (sourceType == _tokens->primvar) { + // We need to add a aov write shader to the list of aov_shaders on the options node. Each + // of this shader will be executed on every surface. + writer = AiNode(renderDelegate->GetUniverse(), arnoldTypes.writer); + if (sourceName == "st" || sourceName == "uv") { // st and uv are written to the built-in UV + reader = AiNode(renderDelegate->GetUniverse(), str::utility); + AiNodeSetStr(reader, str::color_mode, str::uv); + AiNodeSetStr(reader, str::shade_mode, str::flat); + } else { + reader = AiNode(renderDelegate->GetUniverse(), arnoldTypes.reader); + AiNodeSetStr(reader, str::attribute, sourceName.c_str()); + } + const auto writerName = renderDelegate->GetLocalNodeName( + AtString{TfStringPrintf("HdArnoldRenderPass_aov_writer_%p", writer).c_str()}); + const auto readerName = renderDelegate->GetLocalNodeName( + AtString{TfStringPrintf("HdArnoldRenderPass_aov_reader_%p", reader).c_str()}); + AiNodeSetStr(writer, str::name, writerName); + AiNodeSetStr(reader, str::name, readerName); + AiNodeSetStr(writer, str::aov_name, name.c_str()); + AiNodeSetBool(writer, str::blend_opacity, false); + AiNodeLink(reader, str::aov_input, writer); + aovShaders.push_back(writer); + return name; + } else { + return sourceName; + } +} + } // namespace HdArnoldRenderPass::HdArnoldRenderPass( @@ -331,6 +421,23 @@ HdArnoldRenderPass::~HdArnoldRenderPass() // We are not assigning this array to anything, so needs to be manually destroyed. AiArrayDestroy(_fallbackOutputs); + for (auto& deepProduct : _deepProducts) { + if (deepProduct.driver != nullptr) { + AiNodeDestroy(deepProduct.driver); + } + if (deepProduct.filter != nullptr) { + AiNodeDestroy(deepProduct.filter); + } + for (auto& renderVar : deepProduct.renderVars) { + if (renderVar.writer != nullptr) { + AiNodeDestroy(renderVar.writer); + } + if (renderVar.reader != nullptr) { + AiNodeDestroy(renderVar.reader); + } + } + } + _ClearRenderBuffers(); } @@ -428,6 +535,7 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt TF_VERIFY(!aovBindings.empty(), "No AOV bindings to render into!"); #endif + // Delegate Render Products are only introduced in Houdini 18.5, which is 20.8 that has USD_DO_NOT_BLIT always set. #ifndef USD_DO_NOT_BLIT if (aovBindings.empty()) { // We are first checking if the right storage pointer is set on the driver. @@ -460,7 +568,9 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt // AOV bindings exists, so first we are checking if anything has changed. // If something has changed, then we rebuild the local storage class, and the outputs definition. // We expect Hydra to resize the render buffers. - if (_RenderBuffersChanged(aovBindings) || _usingFallbackBuffers) { + const auto& delegateRenderProducts = _renderDelegate->GetDelegateRenderProducts(); + if (_RenderBuffersChanged(aovBindings) || (!delegateRenderProducts.empty() && _deepProducts.empty()) || + _usingFallbackBuffers) { _usingFallbackBuffers = false; renderParam->Interrupt(); _ClearRenderBuffers(); @@ -469,8 +579,8 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt AiNodeSetPtr(_mainDriver, str::id_pointer, nullptr); // Rebuilding render buffers const auto numBindings = static_cast(aovBindings.size()); - auto* outputsArray = AiArrayAllocate(numBindings, 1, AI_TYPE_STRING); - auto* outputs = static_cast(AiArrayMap(outputsArray)); + std::vector outputs; + outputs.reserve(numBindings); std::vector lightPathExpressions; std::vector aovShaders; // When creating the outputs array we follow this logic: @@ -488,51 +598,10 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt // while they are being used. buffer.buffer = dynamic_cast(binding.renderBuffer); buffer.settings = binding.aovSettings; - // We first check if the filterNode exists. - const char* filterName = [&]() -> const char* { - // We need to make sure that it's holding a string, then try to create it to make sure - // it's a node type supported by Arnold. - const auto filterType = - _GetOptionalSetting(binding.aovSettings, _tokens->aovSettingFilter, std::string{}); - if (filterType.empty()) { - return nullptr; - } - buffer.filter = AiNode(_renderDelegate->GetUniverse(), filterType.c_str()); - if (buffer.filter == nullptr) { - return nullptr; - } - const auto filterNameStr = _renderDelegate->GetLocalNodeName( - AtString{TfStringPrintf("HdArnoldRenderPass_filter_%p", buffer.filter).c_str()}); - AiNodeSetStr(buffer.filter, str::name, filterNameStr); - const auto* nodeEntry = AiNodeGetNodeEntry(buffer.filter); - // We are first checking for the filter parameters prefixed with "arnold:", then doing a second - // loop to check for "arnold:filter_type:" prefixed parameters. The reason for two loops is - // we want the second version to overwrite the first one, and with unordered_map, we are not - // getting any sort of ordering. - auto readFilterParameters = [&](const TfToken& filterPrefix) { - for (const auto& setting : binding.aovSettings) { - // We already processed the filter parameter - if (setting.first != _tokens->aovSettingFilter && - TfStringStartsWith(setting.first, filterPrefix)) { - const AtString parameterName(setting.first.GetText() + filterPrefix.size()); - // name is special in arnold - if (parameterName == str::name) { - continue; - } - const auto* paramEntry = AiNodeEntryLookUpParameter(nodeEntry, parameterName); - if (paramEntry != nullptr) { - HdArnoldSetParameter(buffer.filter, paramEntry, setting.second); - } - } - } - }; - - readFilterParameters(_tokens->aovSetting); - readFilterParameters( - TfToken{TfStringPrintf("%s%s:", _tokens->aovSetting.GetText(), filterType.c_str())}); - - return AiNodeGetName(buffer.filter); - }(); + buffer.filter = _CreateFilter(_renderDelegate, binding.aovSettings); + const auto* filterName = buffer.filter != nullptr ? AiNodeGetName(buffer.filter) : boxName; + // Different possible filter for P and ID AOVs. + const auto* filterGeoName = buffer.filter != nullptr ? AiNodeGetName(buffer.filter) : closestName; const auto sourceType = _GetOptionalSetting(binding.aovSettings, _tokens->sourceType, _tokens->raw); const auto sourceName = _GetOptionalSetting( @@ -541,22 +610,15 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt // an aov with the same name. We can't just check for the source name; for example: using a primvar // type and displaying a "color" or a "depth" user data is a valid use case. const auto isRaw = sourceType == _tokens->raw; + AtString output; if (isRaw && sourceName == HdAovTokens->color) { - *outputs = AtString( - TfStringPrintf("RGBA RGBA %s %s", filterName != nullptr ? filterName : boxName, mainDriverName) - .c_str()); + output = AtString{TfStringPrintf("RGBA RGBA %s %s", filterName, mainDriverName).c_str()}; AiNodeSetPtr(_mainDriver, str::color_pointer, binding.renderBuffer); } else if (isRaw && sourceName == HdAovTokens->depth) { - *outputs = - AtString(TfStringPrintf( - "P VECTOR %s %s", filterName != nullptr ? filterName : closestName, mainDriverName) - .c_str()); + output = AtString{TfStringPrintf("P VECTOR %s %s", filterGeoName, mainDriverName).c_str()}; AiNodeSetPtr(_mainDriver, str::depth_pointer, binding.renderBuffer); } else if (isRaw && sourceName == HdAovTokens->primId) { - *outputs = - AtString(TfStringPrintf( - "ID UINT %s %s", filterName != nullptr ? filterName : closestName, mainDriverName) - .c_str()); + output = AtString{TfStringPrintf("ID UINT %s %s", filterGeoName, mainDriverName).c_str()}; AiNodeSetPtr(_mainDriver, str::id_pointer, binding.renderBuffer); } else { // Querying the data format from USD, with a default value of color3f. @@ -568,8 +630,8 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt AtString{TfStringPrintf("HdArnoldRenderPass_aov_driver_%p", buffer.driver).c_str()}); AiNodeSetStr(buffer.driver, str::name, driverNameStr); AiNodeSetPtr(buffer.driver, str::aov_pointer, buffer.buffer); - const char* aovName = nullptr; const auto arnoldTypes = _GetArnoldAOVTypeFromTokenType(format); + const char* aovName = nullptr; if (sourceType == _tokens->lpe) { aovName = binding.aovName.GetText(); // We have to add the light path expression to the outputs node in the format of: @@ -602,15 +664,111 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt } else { aovName = sourceName.c_str(); } - *outputs = AtString(TfStringPrintf( - "%s %s %s %s", aovName, arnoldTypes.outputString, - filterName != nullptr ? filterName : boxName, AiNodeGetName(buffer.driver)) - .c_str()); + output = AtString{ + TfStringPrintf( + "%s %s %s %s", aovName, arnoldTypes.outputString, filterName, AiNodeGetName(buffer.driver)) + .c_str()}; } - outputs += 1; + outputs.push_back(output); + } + // We haven't initialized the deep products yet. + // At the moment this won't work if delegate render products are set interactively, it's not something we + // would potentially encounter as deep exrs are typically not rendered for interactive sessions, and + // delegate render products are only set when rendering in husk. + if (!delegateRenderProducts.empty() && _deepProducts.empty()) { + _deepProducts.reserve(delegateRenderProducts.size()); + for (const auto& product : delegateRenderProducts) { + DeepProduct deepProduct; + if (product.renderVars.empty()) { + continue; + } + deepProduct.driver = AiNode(_renderDelegate->GetUniverse(), str::driver_deepexr); + if (Ai_unlikely(deepProduct.driver == nullptr)) { + continue; + } + const AtString deepDriverName = + AtString{TfStringPrintf("HdArnoldRenderPass_deep_driver_%p", deepProduct.driver).c_str()}; + AiNodeSetStr(deepProduct.driver, str::name, deepDriverName); + AiNodeSetStr(deepProduct.driver, str::filename, product.productName.GetText()); + // One filter per deep driver. + deepProduct.filter = _CreateFilter(_renderDelegate, product.settings); + const auto* filterName = + deepProduct.filter != nullptr ? AiNodeGetName(deepProduct.filter) : boxName; + // Applying custom parameters to the driver. + _ReadNodeParameters(deepProduct.driver, _tokens->aovSetting, product.settings); + constexpr float defaultTolerance = 0.01f; + constexpr bool defaultEnableFiltering = true; + constexpr bool defaultHalfPrecision = false; + const auto numRenderVars = static_cast(product.renderVars.size()); + auto* toleranceArray = AiArrayAllocate(numRenderVars, 1, AI_TYPE_FLOAT); + auto* tolerance = static_cast(AiArrayMap(toleranceArray)); + auto* enableFilteringArray = AiArrayAllocate(numRenderVars, 1, AI_TYPE_BOOLEAN); + auto* enableFiltering = static_cast(AiArrayMap(enableFilteringArray)); + auto* halfPrecisionArray = AiArrayAllocate(numRenderVars, 1, AI_TYPE_BOOLEAN); + auto* halfPrecision = static_cast(AiArrayMap(halfPrecisionArray)); + for (const auto& renderVar : product.renderVars) { + DeepRenderVar deepRenderVar; + *tolerance = + _GetOptionalSetting(renderVar.settings, _tokens->tolerance, defaultTolerance); + *enableFiltering = _GetOptionalSetting( + renderVar.settings, _tokens->enableFiltering, defaultEnableFiltering); + *halfPrecision = + _GetOptionalSetting(renderVar.settings, _tokens->halfPrecision, defaultHalfPrecision); + const auto isRaw = renderVar.sourceType == _tokens->raw; + if (isRaw && renderVar.sourceName == HdAovTokens->color) { + deepRenderVar.output = + AtString{TfStringPrintf("RGBA RGBA %s %s", filterName, deepDriverName.c_str()).c_str()}; + } else if (isRaw && renderVar.sourceName == HdAovTokens->depth) { + deepRenderVar.output = AtString{ + TfStringPrintf("Z FLOAT %s %s", filterName, deepDriverName.c_str()).c_str()}; + } else if (isRaw && renderVar.sourceName == HdAovTokens->primId) { + deepRenderVar.output = AtString{ + TfStringPrintf("ID UINT %s %s", filterName, deepDriverName.c_str()).c_str()}; + } else { + // Querying the data format from USD, with a default value of color3f. + const auto format = _GetOptionalSetting( + renderVar.settings, _tokens->dataType, _GetTokenFromHdFormat(renderVar.format)); + const auto arnoldTypes = _GetArnoldAOVTypeFromTokenType(format); + const auto aovName = _CreateAOV( + _renderDelegate, arnoldTypes, renderVar.name, renderVar.sourceType, + renderVar.sourceName, deepRenderVar.writer, deepRenderVar.reader, lightPathExpressions, + aovShaders); + deepRenderVar.output = + AtString{TfStringPrintf( + "%s %s %s %s", aovName.c_str(), arnoldTypes.outputString, filterName, + deepDriverName.c_str()) + .c_str()}; + } + tolerance += 1; + enableFiltering += 1; + halfPrecision += 1; + deepProduct.renderVars.push_back(deepRenderVar); + } + AiArrayUnmap(toleranceArray); + AiArrayUnmap(enableFilteringArray); + AiArrayUnmap(halfPrecisionArray); + AiNodeSetArray(deepProduct.driver, str::layer_tolerance, toleranceArray); + AiNodeSetArray(deepProduct.driver, str::layer_enable_filtering, enableFilteringArray); + AiNodeSetArray(deepProduct.driver, str::layer_half_precision, halfPrecisionArray); + _deepProducts.push_back(std::move(deepProduct)); + } + } + // Add deep products to the outputs list. + if (!_deepProducts.empty()) { + for (const auto& product : _deepProducts) { + for (const auto& renderVar : product.renderVars) { + if (renderVar.writer != nullptr) { + aovShaders.push_back(renderVar.writer); + } + outputs.push_back(renderVar.output); + } + } + } + if (!outputs.empty()) { + AiNodeSetArray( + _renderDelegate->GetOptions(), str::outputs, + AiArrayConvert(static_cast(outputs.size()), 1, AI_TYPE_STRING, outputs.data())); } - AiArrayUnmap(outputsArray); - AiNodeSetArray(_renderDelegate->GetOptions(), str::outputs, outputsArray); AiNodeSetArray( _renderDelegate->GetOptions(), str::light_path_expressions, lightPathExpressions.empty() ? AiArray(0, 1, AI_TYPE_STRING) diff --git a/render_delegate/render_pass.h b/render_delegate/render_pass.h index d36d7e3d9a..074cc613f4 100755 --- a/render_delegate/render_pass.h +++ b/render_delegate/render_pass.h @@ -103,6 +103,27 @@ class HdArnoldRenderPass : public HdRenderPass { AtNode* _closestFilter = nullptr; ///< Pointer to the closest Arnold Filter. AtNode* _mainDriver = nullptr; ///< Pointer to the Arnold Driver writing color, position and depth. + struct DeepRenderVar { + /// Definition for the output string. + AtString output; + /// Optional writer node for each AOV. + AtNode* writer = nullptr; + /// Optional reader node for each AOV. + AtNode* reader = nullptr; + }; + + // Each deep driver handles multiple AOVs. + struct DeepProduct { + /// List of the RenderVars. + std::vector renderVars; + /// Deep EXR driver. + AtNode* driver = nullptr; + /// Filter for the Deep EXR driver. + AtNode* filter = nullptr; + }; + + std::vector _deepProducts; ///< List of Deep Render Products. + #ifndef USD_DO_NOT_BLIT #ifdef USD_HAS_FULLSCREEN_SHADER HdxFullscreenShader _fullscreenShader; ///< Hydra utility to blit to OpenGL. diff --git a/render_delegate/renderer_plugin.cpp b/render_delegate/renderer_plugin.cpp index 40f7065e9e..242117239b 100755 --- a/render_delegate/renderer_plugin.cpp +++ b/render_delegate/renderer_plugin.cpp @@ -31,8 +31,15 @@ #include "render_delegate.h" +#include + PXR_NAMESPACE_OPEN_SCOPE +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + ((houdini_renderer, "houdini:renderer"))); +// clang-format on + // Register the Ai plugin with the renderer plugin system. TF_REGISTRY_FUNCTION(TfType) { HdRendererPluginRegistry::Define(); } @@ -40,7 +47,15 @@ HdRenderDelegate* HdArnoldRendererPlugin::CreateRenderDelegate() { return new Hd HdRenderDelegate* HdArnoldRendererPlugin::CreateRenderDelegate(const HdRenderSettingsMap& settingsMap) { - auto* delegate = new HdArnoldRenderDelegate(); + auto context = HdArnoldRenderContext::Hydra; + const auto* houdiniRenderer = TfMapLookupPtr(settingsMap, _tokens->houdini_renderer); + if (houdiniRenderer != nullptr && + ((houdiniRenderer->IsHolding() && houdiniRenderer->UncheckedGet() == str::t_husk) || + (houdiniRenderer->IsHolding() && + houdiniRenderer->UncheckedGet() == str::t_husk.GetString()))) { + context = HdArnoldRenderContext::Husk; + } + auto* delegate = new HdArnoldRenderDelegate(context); for (const auto& setting : settingsMap) { delegate->SetRenderSetting(setting.first, setting.second); }