diff --git a/include/hvt/engine/basicLayerParams.h b/include/hvt/engine/basicLayerParams.h index f4b91e92..400e5af4 100644 --- a/include/hvt/engine/basicLayerParams.h +++ b/include/hvt/engine/basicLayerParams.h @@ -97,9 +97,6 @@ struct HVT_API BasicLayerParams /// Defines the render buffer size. PXR_NS::GfVec2i renderBufferSize; - /// The AOV buffer ID to visualize (color or depth). - PXR_NS::TfToken visualizeAOV; - /// Enable selection is on by default. bool enableSelection { true }; diff --git a/include/hvt/engine/framePass.h b/include/hvt/engine/framePass.h index 28d5cd98..ccd78586 100644 --- a/include/hvt/engine/framePass.h +++ b/include/hvt/engine/framePass.h @@ -143,6 +143,21 @@ struct HVT_API ViewParams /// parameters specific to the FramePass. struct HVT_API FramePassParams : public BasicLayerParams { + /// Defines the inputs, outputs and the aov to visualize. + /// @{ + + /// The AOV buffer to visualize e.g., color, depth, etc. + PXR_NS::TfToken visualizeAOV; + + /// The list of AOV buffers to render e.g., color, depth, etc. + /// \note This is used to override the default render outputs. + PXR_NS::TfTokenVector renderOutputs; + + /// Enable eye relative normal render output. + /// \note this adds an extra cost for all geometry render passes. + bool enableNeyeRenderOutput { false }; + /// @} + /// View, model and world settings. /// @{ ViewParams viewInfo; @@ -167,10 +182,6 @@ struct HVT_API FramePassParams : public BasicLayerParams bool enableMultisampling { true }; size_t msaaSampleCount { 4 }; /// @} - - /// Enable eye relative normal render output. - /// \note this adds an extra cost for all geometry render passes. - bool enableNeyeRenderOutput { false }; }; /// A FramePass is used to render or select from a collection of Prims using a set of HdTasks and @@ -250,16 +261,6 @@ class HVT_API FramePass /// \return An handle to the associated render texture or null if not found. PXR_NS::HgiTextureHandle GetRenderTexture(PXR_NS::TfToken const& aovToken) const; - /// It holds the token (e.g., color, depth) and its corresponding texture handle. - struct RenderOutput - { - /// The AOV tag i.e., color or depth. - PXR_NS::TfToken aovToken; - /// The corresponding render texture handle. - PXR_NS::HgiTextureHandle aovTextureHandle; - }; - using RenderOutputs = std::vector; - /// Return the render index used by this frame pass. PXR_NS::HdRenderIndex* GetRenderIndex() const; diff --git a/include/hvt/engine/renderBufferManager.h b/include/hvt/engine/renderBufferManager.h index ee7e1c64..3021a30e 100644 --- a/include/hvt/engine/renderBufferManager.h +++ b/include/hvt/engine/renderBufferManager.h @@ -55,6 +55,14 @@ class HVT_API RenderBufferManager : public RenderBufferSettingsProvider /// Destructor. ~RenderBufferManager(); + /// Get the all the possible renderer AOV tokens. + /// \return Returns the renderer AOV tokens. + static PXR_NS::TfTokenVector GetAllRendererAovs(); + + /// Get the all the renderer AOV tokens supported by the selected render delegate e.g. HdStorm. + /// \return Returns the supported renderer AOV tokens. + PXR_NS::TfTokenVector GetSupportedRendererAovs() const; + /// Gets the dimensions of the render buffers. /// \return Returns the render buffer dimensions. inline PXR_NS::GfVec2i const& GetRenderBufferDimensions() const { return _size; } @@ -73,9 +81,19 @@ class HVT_API RenderBufferManager : public RenderBufferSettingsProvider PXR_NS::GfVec2i const& newRenderBufferSize, size_t msaaSampleCount, bool msaaEnabled); /// Set the render outputs. - /// It does NOT update any RenderTaskParams, but updates the AovParamCache and the viewport AOV. - bool SetRenderOutputs(PXR_NS::TfTokenVector const& names, RenderBufferBindings const& inputs, - PXR_NS::GfVec4d const& viewport); + /// \note It does NOT update any RenderTaskParams, but updates the AovParamCache and the viewport AOV. + /// \param outputToVisualize The AOV to visualize in the viewport. + /// \param outputs The names of the AOVs to be used for the render outputs. + /// \param inputs The bindings of the AOVs to be used for the render inputs. + /// \param viewport The viewport dimensions to be used for the render outputs. + /// \return True if the render outputs were set successfully, false otherwise. + /// \note An empty list of inputs means to create its own render buffers for the inputs. + bool SetRenderOutputs( + PXR_NS::TfToken const& outputToVisualize, PXR_NS::TfTokenVector const& outputs, + RenderBufferBindings const& inputs, PXR_NS::GfVec4d const& viewport); + + /// Get the renderer outputs. + PXR_NS::TfTokenVector const& GetRenderOutputs() const; /// Set the render output clear color in the AovParamCache. void SetRenderOutputClearColor(PXR_NS::TfToken const& name, PXR_NS::VtValue const& clearValue); @@ -134,4 +152,4 @@ class HVT_API RenderBufferManager : public RenderBufferSettingsProvider std::unique_ptr _impl; }; -} // namespace HVT_NS \ No newline at end of file +} // namespace HVT_NS diff --git a/include/hvt/resources/shaders/copy.glslfx b/include/hvt/resources/shaders/copy.glslfx deleted file mode 100644 index 9e32dfb9..00000000 --- a/include/hvt/resources/shaders/copy.glslfx +++ /dev/null @@ -1,44 +0,0 @@ --- glslfx version 0.1 - -// Copyright 2025 Autodesk, 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. - --- configuration -{ - "techniques": { - "default": { - "CopyVertex": { - "source": [ "Copy.Vertex" ] - }, - "CopyFragment": { - "source": [ "Copy.Fragment" ] - } - } - } -} - --- glsl Copy.Vertex - -void main(void) -{ - gl_Position = position; - uvOut = uvIn; -} - --- glsl Copy.Fragment - -void main(void) -{ - hd_FragColor = HgiTexelFetch_colorIn(ivec2(uvOut * screenSize)); -} diff --git a/source/engine/CMakeLists.txt b/source/engine/CMakeLists.txt index 2181416c..471a22ea 100644 --- a/source/engine/CMakeLists.txt +++ b/source/engine/CMakeLists.txt @@ -20,6 +20,8 @@ set(_ENGINE_INCLUDE_DIR "${_HVT_INCLUDE_DIR}/hvt/engine") # Collect the source files. set(_SOURCE_FILES + "copyDepthShader.cpp" + "copyDepthShader.h" "delegateStreamUtils.h" "framePass.cpp" "framePassUtils.cpp" diff --git a/source/engine/copyDepthShader.cpp b/source/engine/copyDepthShader.cpp new file mode 100644 index 00000000..9d924d97 --- /dev/null +++ b/source/engine/copyDepthShader.cpp @@ -0,0 +1,337 @@ +// Copyright 2025 Autodesk, 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. + +#include "copyDepthShader.h" + +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace HVT_NS +{ + +namespace +{ +static const std::string vsCode = R"( +void main(void) { + uvOut = vec2((hd_VertexID << 1) & 2, hd_VertexID & 2); + gl_Position = vec4(uvOut * 2.0f + -1.0f, 0.0f, 1.0f); +})"; + +static const std::string fsCode = R"( +void main(void) { + vec2 fragCoord = uvOut * screenSize; + float depth = HgiTexelFetch_depthIn(ivec2(fragCoord)).x; + gl_FragDepth = depth; +})"; + +// Prepare uniform buffer for GPU computation. +struct Uniforms +{ + GfVec2f screenSize; +}; + +} // namespace + +CopyDepthShader::CopyDepthShader(Hgi* hgi) : _hgi(hgi) {} +CopyDepthShader::~CopyDepthShader() +{ + _Cleanup(); +} + +bool CopyDepthShader::_CreateShaderProgram(HgiTextureDesc const& inputTextureDesc) +{ + if (_shaderProgram) + { + return true; + } + + // Vertex Shader. + + HgiShaderFunctionDesc vertDesc; + vertDesc.debugName = "CopyDepthShader Vertex"; + vertDesc.shaderStage = HgiShaderStageVertex; + + HgiShaderFunctionAddStageInput( + &vertDesc, "hd_VertexID", "uint", HgiShaderKeywordTokens->hdVertexID); + HgiShaderFunctionAddStageOutput(&vertDesc, "gl_Position", "vec4", "position"); + + HgiShaderFunctionAddStageOutput(&vertDesc, "uvOut", "vec2"); + + vertDesc.shaderCode = vsCode.c_str(); + HgiShaderFunctionHandle vertFn = _hgi->CreateShaderFunction(vertDesc); + + // Check for error. + if (!vertFn->IsValid()) + { + TF_CODING_ERROR("%s", vertFn->GetCompileErrors().c_str()); + _Cleanup(); + return false; + } + + // Fragment Shader. + + HgiShaderFunctionDesc fragDesc; + fragDesc.debugName = "CopyDepthShader Fragment"; + fragDesc.shaderStage = HgiShaderStageFragment; + + HgiShaderFunctionAddStageInput(&fragDesc, "uvOut", "vec2"); + + HgiShaderFunctionAddTexture(&fragDesc, "depthIn", + /*bindIndex = */ 0, + /*dimensions = */ 2, inputTextureDesc.format); + + HgiShaderFunctionAddStageOutput(&fragDesc, "gl_FragDepth", "float", "depth(any)"); + HgiShaderFunctionAddConstantParam(&fragDesc, "screenSize", "vec2"); + + fragDesc.shaderCode = fsCode.c_str(); + HgiShaderFunctionHandle fragFn = _hgi->CreateShaderFunction(fragDesc); + + // Check for error. + if (!fragFn->IsValid()) + { + TF_CODING_ERROR("%s", fragFn->GetCompileErrors().c_str()); + _Cleanup(); + return false; + } + + // Shader program. + + HgiShaderProgramDesc programDesc; + programDesc.debugName = "CopyDepthShader Program"; + programDesc.shaderFunctions.push_back(std::move(vertFn)); + programDesc.shaderFunctions.push_back(std::move(fragFn)); + _shaderProgram = _hgi->CreateShaderProgram(programDesc); + + // Check for error. + if (!_shaderProgram->IsValid()) + { + TF_CODING_ERROR("%s", _shaderProgram->GetCompileErrors().c_str()); + _Cleanup(); + return false; + } + + return true; +} + +bool CopyDepthShader::_CreateResourceBindings(HgiTextureHandle const& inputTexture) +{ + HgiResourceBindingsDesc resourceDesc; + resourceDesc.debugName = "CopyDepthShader Resources"; + + HgiTextureBindDesc texBind; + texBind.bindingIndex = 0; + texBind.stageUsage = HgiShaderStageFragment; + texBind.textures.push_back(inputTexture); + texBind.samplers.push_back(_sampler); + + resourceDesc.textures.push_back(std::move(texBind)); + + // If nothing has changed in the descriptor we avoid re-creating the resource bindings object. + if (_resourceBindings) + { + HgiResourceBindingsDesc const& desc = _resourceBindings->GetDescriptor(); + if (desc == resourceDesc) + { + return true; + } + else + { + _hgi->DestroyResourceBindings(&_resourceBindings); + } + } + + _resourceBindings = _hgi->CreateResourceBindings(resourceDesc); + + return true; +} + +bool CopyDepthShader::_CreatePipeline(HgiTextureHandle const& outputTexture) +{ + if (_pipeline) + { + if (_depthAttachment.format == outputTexture->GetDescriptor().format) + { + return true; + } + + _hgi->DestroyGraphicsPipeline(&_pipeline); + } + + HgiGraphicsPipelineDesc pipelineDesc; + pipelineDesc.debugName = "CopyDepthShader Pipeline"; + pipelineDesc.shaderProgram = _shaderProgram; + + // Set up depth attachment. + _depthAttachment.format = outputTexture->GetDescriptor().format; + _depthAttachment.usage = outputTexture->GetDescriptor().usage; + _depthAttachment.loadOp = HgiAttachmentLoadOpDontCare; + _depthAttachment.storeOp = HgiAttachmentStoreOpStore; + + pipelineDesc.depthAttachmentDesc = _depthAttachment; + + // Alpha to coverage would prevent any pixels that have an alpha of 0.0 from + // being written. We want to color correct all pixels. Even background + // pixels that were set with a clearColor alpha of 0.0. + pipelineDesc.multiSampleState.alphaToCoverageEnable = false; + + // The MSAA on renderPipelineState has to match the render target. + pipelineDesc.multiSampleState.sampleCount = outputTexture->GetDescriptor().sampleCount; + pipelineDesc.multiSampleState.multiSampleEnable = pipelineDesc.multiSampleState.sampleCount > 1; + + pipelineDesc.depthState.depthTestEnabled = true; + pipelineDesc.depthState.depthWriteEnabled = true; + pipelineDesc.depthState.depthCompareFn = HgiCompareFunctionAlways; + + // Uniform. + pipelineDesc.shaderConstantsDesc.stageUsage = HgiShaderStageFragment; + pipelineDesc.shaderConstantsDesc.byteSize = sizeof(Uniforms); + + _pipeline = _hgi->CreateGraphicsPipeline(pipelineDesc); + + return (bool)_pipeline; +} + +bool CopyDepthShader::_CreateSampler() +{ + if (_sampler) + { + return true; + } + + HgiSamplerDesc sampDesc; + + sampDesc.magFilter = HgiSamplerFilterLinear; + sampDesc.minFilter = HgiSamplerFilterLinear; + + sampDesc.addressModeU = HgiSamplerAddressModeClampToEdge; + sampDesc.addressModeV = HgiSamplerAddressModeClampToEdge; + + _sampler = _hgi->CreateSampler(sampDesc); + + return true; +} + +void CopyDepthShader::_Execute( + HgiTextureHandle const& inputTexture, HgiTextureHandle const& outputTexture) +{ + GfVec3i const& dimensions = inputTexture->GetDescriptor().dimensions; + + // Prepare graphics cmds. + + HgiGraphicsCmdsDesc gfxDesc; + gfxDesc.depthAttachmentDesc = _depthAttachment; + gfxDesc.depthTexture = outputTexture; + + const GfVec4i viewport(0, 0, dimensions[0], dimensions[1]); + + Uniforms uniform; + uniform.screenSize[0] = static_cast(dimensions[0]); + uniform.screenSize[1] = static_cast(dimensions[1]); + + // Begin rendering. + + HgiGraphicsCmdsUniquePtr gfxCmds = _hgi->CreateGraphicsCmds(gfxDesc); + gfxCmds->PushDebugGroup("CopyDepthShader"); + gfxCmds->BindResources(_resourceBindings); + gfxCmds->BindPipeline(_pipeline); + gfxCmds->SetConstantValues(_pipeline, HgiShaderStageFragment, 0, sizeof(uniform), &uniform); + gfxCmds->SetViewport(viewport); + gfxCmds->Draw(3, 0, 1, 0); + gfxCmds->PopDebugGroup(); + + // Done recording commands, submit work. + _hgi->SubmitCmds(gfxCmds.get()); +} + +void CopyDepthShader::Execute( + HgiTextureHandle const& inputTexture, HgiTextureHandle const& outputTexture) +{ + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + if (inputTexture == outputTexture) + { + return; + } + + class Guard + { + public: + Guard(HgiTextureHandle const& inputTexture) : _inputTexture(inputTexture) + { + _inputTexture->SubmitLayoutChange(HgiTextureUsageBitsShaderRead); + } + ~Guard() { _inputTexture->SubmitLayoutChange(HgiTextureUsageBitsDepthTarget); } + + private: + HgiTextureHandle const& _inputTexture; + } guard(inputTexture); + + if (!TF_VERIFY(_CreateSampler(), "Sampler creation failed.")) + { + return; + } + if (!TF_VERIFY(_CreateShaderProgram(inputTexture->GetDescriptor()), "Shader creation failed.")) + { + return; + } + if (!TF_VERIFY(_CreateResourceBindings(inputTexture), "Resource binding failed.")) + { + return; + } + if (!TF_VERIFY(_CreatePipeline(outputTexture), "Pipeline creation failed.")) + { + return; + } + + _Execute(inputTexture, outputTexture); +} + +void CopyDepthShader::_Cleanup() +{ + if (_sampler) + { + _hgi->DestroySampler(&_sampler); + } + + if (_shaderProgram) + { + auto shaderFunctions = _shaderProgram->GetShaderFunctions(); + + for (auto& fn : shaderFunctions) + { + _hgi->DestroyShaderFunction(&fn); + } + _hgi->DestroyShaderProgram(&_shaderProgram); + } + + if (_resourceBindings) + { + _hgi->DestroyResourceBindings(&_resourceBindings); + } + + if (_pipeline) + { + _hgi->DestroyGraphicsPipeline(&_pipeline); + } +} + +} // namespace HVT_NS diff --git a/source/engine/copyDepthShader.h b/source/engine/copyDepthShader.h new file mode 100644 index 00000000..bb831a7c --- /dev/null +++ b/source/engine/copyDepthShader.h @@ -0,0 +1,69 @@ +// Copyright 2025 Autodesk, 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. +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace HVT_NS +{ + +/// Copy the depth AOV from the input to the output texture. +/// \note That's the strip version of HdxFullscreenShader which always needs the color AOV. +class CopyDepthShader +{ +public: + explicit CopyDepthShader(PXR_NS::Hgi* hgi); + + CopyDepthShader() = delete; + CopyDepthShader(const CopyDepthShader&) = delete; + CopyDepthShader& operator=(const CopyDepthShader&) = delete; + + ~CopyDepthShader(); + + void Execute(PXR_NS::HgiTextureHandle const& inputTexture, + PXR_NS::HgiTextureHandle const& outputTexture); + +protected: + bool _CreateShaderProgram(PXR_NS::HgiTextureDesc const& inputTextureDesc); + bool _CreateBufferResources(); + bool _CreateResourceBindings(PXR_NS::HgiTextureHandle const& inputTexture); + bool _CreatePipeline(PXR_NS::HgiTextureHandle const& outputTexture); + bool _CreateSampler(); + void _Execute(PXR_NS::HgiTextureHandle const& inputTexture, + PXR_NS::HgiTextureHandle const& outputTexture); + void _Cleanup(); + +private: + PXR_NS::Hgi* _hgi { nullptr }; + + PXR_NS::HgiAttachmentDesc _depthAttachment; + PXR_NS::HgiSamplerHandle _sampler; + PXR_NS::HgiShaderProgramHandle _shaderProgram; + PXR_NS::HgiResourceBindingsHandle _resourceBindings; + PXR_NS::HgiGraphicsPipelineHandle _pipeline; +}; + +} // namespace HVT_NS diff --git a/source/engine/framePass.cpp b/source/engine/framePass.cpp index c5675e5a..5afafee2 100644 --- a/source/engine/framePass.cpp +++ b/source/engine/framePass.cpp @@ -303,19 +303,36 @@ HdTaskSharedPtrVector FramePass::GetRenderTasks(RenderBufferBindings const& inpu } else { - if (!IsStormRenderDelegate(GetRenderIndex()) || params().enableOutline) - renderOutputs = { HdAovTokens->color, HdAovTokens->depth, HdAovTokens->primId, - HdAovTokens->elementId, HdAovTokens->instanceId}; + if ( _passParams.enableOutline) + { + renderOutputs = _bufferManager->GetSupportedRendererAovs(); + } + else if (_passParams.renderOutputs.empty()) + { + // Add the default AOVs. + if (!IsStormRenderDelegate(GetRenderIndex())) + { + renderOutputs = _bufferManager->GetSupportedRendererAovs(); + } + else + { + renderOutputs = { HdAovTokens->color, HdAovTokens->depth }; + } + } else - renderOutputs = { HdAovTokens->color, HdAovTokens->depth}; + { + renderOutputs = _passParams.renderOutputs; + } + // Add the Neye AOV if needed. if (_passParams.enableNeyeRenderOutput) { renderOutputs.push_back(HdAovTokens->Neye); } } - bool hasRemovedBuffers = _bufferManager->SetRenderOutputs(renderOutputs, inputAOVs, {}); + const bool hasRemovedBuffers + = _bufferManager->SetRenderOutputs(_passParams.visualizeAOV, renderOutputs, inputAOVs, {}); if (hasRemovedBuffers) { diff --git a/source/engine/renderBufferManager.cpp b/source/engine/renderBufferManager.cpp index ab9b9478..3b976cd3 100644 --- a/source/engine/renderBufferManager.cpp +++ b/source/engine/renderBufferManager.cpp @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include +#include #include #include #include +#include "copyDepthShader.h" + // clang-format off #if defined(__clang__) #pragma clang diagnostic push @@ -53,11 +55,13 @@ #include #include +// clang-format off #if defined(__clang__) - #pragma clang diagnostic pop +#pragma clang diagnostic pop #elif defined(_MSC_VER) - #pragma warning(pop) +#pragma warning(pop) #endif +// clang-format on PXR_NAMESPACE_USING_DIRECTIVE @@ -112,8 +116,11 @@ class RenderBufferManager::Impl : public RenderBufferSettingsProvider /// Updates render output parameters and creates new render buffers if needed. /// Note: AOV binding values are stored here and consulted later by RenderTasks. - bool SetRenderOutputs(TfTokenVector const& names, RenderBufferBindings const& inputs, - GfVec4d const& viewport, SdfPath const& controllerId); + bool SetRenderOutputs(TfToken const& outputToVisualize, TfTokenVector const& outputs, + RenderBufferBindings const& inputs, GfVec4d const& viewport, SdfPath const& controllerId); + + /// Get the render outputs. + TfTokenVector const& GetRenderOutputs() const { return _aovOutputs; } /// Updates the render output clear color. /// Note: Clear color values are stored here and consulted later by RenderTasks. @@ -172,11 +179,16 @@ class RenderBufferManager::Impl : public RenderBufferSettingsProvider private: - /// Copy the contents of the input buffer into the output buffer + /// Copy the color & depth AOVs of the input buffers into the output buffers. void PrepareBuffersFromInputs(RenderBufferBinding const& colorInput, RenderBufferBinding const& depthInput, HdRenderBufferDescriptor const& desc, SdfPath const& controllerId); + /// Copy the depth AOV of the input buffer into the output buffer. + void PrepareDepthOnlyFromInput(RenderBufferBinding const& inputDepthAov, + HdRenderBufferDescriptor const& desc, + SdfPath const& controllerId); + /// Sets the viewport render output (color or buffer visualization). void SetViewportRenderOutput(const TfToken& name, const SdfPath& controllerId); @@ -225,9 +237,10 @@ class RenderBufferManager::Impl : public RenderBufferSettingsProvider /// The SyncDelegate used to create RenderBufferDescriptor data for use by the render index. SyncDelegatePtr _syncDelegate; - /// The shaders used to copy the contents of the input into the output render buffer. - std::unique_ptr _copyShader; - std::unique_ptr _copyShaderNoDepth; + /// The shaders used to copy the contents of the input into the output render buffer. + std::unique_ptr _copyColorShader; + std::unique_ptr _copyColorShaderNoDepth; + std::unique_ptr _copyDepthShader; }; @@ -267,7 +280,7 @@ Hgi* GetHgi(HdRenderIndex const* renderIndex) Hgi* hgi = hvt::HgiInstance::instance().hgi(); if (hgi) return hgi; - + // If it wasn't created by the HgiInstance look for it on the render index. HdDriverVector const& drivers = renderIndex->GetDrivers(); for (HdDriver* hdDriver : drivers) @@ -384,7 +397,7 @@ void RenderBufferManager::Impl::PrepareBuffersFromInputs(RenderBufferBinding con } else { - //The output render buffer is not holding a writeable buffer. + //The output render buffer is not holding a writeable buffer. //You will need to composite to blend passes results. return; } @@ -397,29 +410,28 @@ void RenderBufferManager::Impl::PrepareBuffersFromInputs(RenderBufferBinding con TF_CODING_ERROR("There is no valid Hgi driver."); return; } - + // Initialize the shader that will copy the contents from the input to the output. - if (!_copyShader) + if (!_copyColorShader) { - _copyShader = - std::make_unique(hgi, "Copy Color Buffer"); + _copyColorShader = std::make_unique(hgi, "Copy Color Buffer"); } // Initialize the shader that will copy the contents from the input to the output. - if (!_copyShaderNoDepth) + if (!_copyColorShaderNoDepth) { - _copyShaderNoDepth = + _copyColorShaderNoDepth = std::make_unique(hgi, "Copy Color Buffer No Depth"); } HdxFullscreenShader* shader = - (!depthInput ? _copyShaderNoDepth.get() : _copyShader.get()); + (!depthInput ? _copyColorShaderNoDepth.get() : _copyColorShader.get()); // Set the screen size constant on the shader. GfVec2f screenSize { static_cast(desc.dimensions[0]), static_cast(desc.dimensions[1]) }; shader->SetShaderConstants(sizeof(screenSize), &screenSize); - + // Submit the layout change to read from the textures. colorInput->SubmitLayoutChange(HgiTextureUsageBitsShaderRead); @@ -439,8 +451,89 @@ void RenderBufferManager::Impl::PrepareBuffersFromInputs(RenderBufferBinding con colorInput->SubmitLayoutChange(HgiTextureUsageBitsColorTarget); } -bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, - RenderBufferBindings const& inputs, GfVec4d const& viewport, SdfPath const& controllerId) +// The code does not use the HdxFullscreenShader helper here because it only needs to copy the depth +// AOVs and HdxFullscreenShader always needs the color AOVs. +void RenderBufferManager::Impl::PrepareDepthOnlyFromInput(RenderBufferBinding const& inputDepthAov, + HdRenderBufferDescriptor const& desc, SdfPath const& controllerId) +{ + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + HgiTextureHandle input = inputDepthAov.texture; + + if (!input) + { + return; + } + + const SdfPath aovPath = GetAovPath(controllerId, inputDepthAov.aovName); + // Get the buffer that the renderer will draw into from the render index. + HdRenderBuffer* buffer = static_cast( + _pRenderIndex->GetBprim(HdPrimTypeTokens->renderBuffer, aovPath)); + + // If there is no buffer in this render index it was determined that the buffer + // to write into should come from the input buffer from the previous pass. + if (!buffer) + { + // Use the input buffer + buffer = inputDepthAov.buffer; + } + else + { + if (!buffer->IsMapped()) + { + // This might be a newly created BPrim. Allocate the GPU texture if needed. + buffer->Allocate(desc.dimensions, desc.format, desc.multiSampled); + } + } + + HgiTextureHandle output; + VtValue outputValue = buffer->GetResource(desc.multiSampled); + if (outputValue.IsHolding()) + { + output = outputValue.Get(); + if (!output) + { + TF_CODING_ERROR("The output render buffer does not have a valid texture %s.", + inputDepthAov.aovName.GetText()); + return; + } + } + else + { + // The output render buffer is not holding a writeable buffer. + // You will need to composite to blend passes results. + return; + } + + // If the input and output are the same texture, no need to copy. + if (output == input) + { + return; + } + + Hgi* hgi = GetHgi(_pRenderIndex); + if (!hgi) + { + TF_CODING_ERROR("There is no valid Hgi driver."); + return; + } + + // Note: HdxFullscreenShader must include the color AOV so it cannot be used here + // because the code only needs to copy the depth AOV. + + if (!_copyDepthShader) + { + _copyDepthShader = std::make_unique(hgi); + } + + // Copy the input to the output texture. + _copyDepthShader->Execute(input, output); +} + +bool RenderBufferManager::Impl::SetRenderOutputs(TfToken const& outputToVisualize, + TfTokenVector const& outputs, RenderBufferBindings const& inputs, GfVec4d const& viewport, + SdfPath const& controllerId) { bool hasRemovedBuffers = false; @@ -486,7 +579,7 @@ bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, // If progressive rendering is enabled, render buffer clear is only required when // `_aovOutputs != outputs`. bool needClear = !_isProgressiveRenderingEnabled || _aovOutputs != outputs; - + // This will delete Bprims from the RenderIndex and clear the _viewportAov and _aovBufferIds // SdfPathVector. if (needClear) @@ -525,10 +618,9 @@ bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, // Add the new RenderBuffers. // NOTE: GetAovPath returns ids of the form {controller_id}/aov_{name}. - std::string rendererName = _pRenderIndex->GetRenderDelegate()->GetRendererDisplayName(); - RenderBufferBinding colorInput {}; - RenderBufferBinding depthInput {}; - HdRenderBufferDescriptor colorDesc; + const std::string rendererName = _pRenderIndex->GetRenderDelegate()->GetRendererDisplayName(); + RenderBufferBinding colorInput, depthInput; + HdRenderBufferDescriptor colorDesc, depthDesc; for (size_t i = 0; i < localOutputs.size(); ++i) { HdRenderBufferDescriptor desc; @@ -544,19 +636,23 @@ bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, inputFound = (rendererName == input.rendererName); if (localOutputs[i] == pxr::HdAovTokens->depth) { + depthDesc = desc; depthInput = input; + if (inputFound) { // If the renderer remains the same, we don't want to copy the depth buffer. // The existing depth buffer will continue to be used. // We do this in order to not loose sub-pixel depth information. - // However, this means that if any Tasks write to the depth after a sub-pixel - // resolve then the depth buffer will be inconsistent with the color buffer and - // that depth information will be lost. I don't think this currently happens in - // practice, so we are opting in favor of keeping the sub-pixel resolution. - // - // FUTURE: We may want to revisit this decision in the future. - // The long-term solution may be to do post processing at the sub-pixel accuracy. + // However, this means that if any Tasks write to the depth after a + // sub-pixel resolve then the depth buffer will be inconsistent with the + // color buffer and that depth information will be lost. I don't think this + // currently happens in practice, so we are opting in favor of keeping the + // sub-pixel resolution. + // + // FUTURE: We may want to revisit this decision in the future. + // The long-term solution may be to do post processing at the sub-pixel + // accuracy. depthInput.texture = HgiTextureHandle(); } } @@ -569,9 +665,9 @@ bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, } } - // If something has changed and the input was not found or the previous renderer is different - // than the current one, then we need to create a new render buffer. - // This will be the buffer used and the previous contents potentially copied into. + // If something has changed and the input was not found or the previous renderer is + // different than the current one, then we need to create a new render buffer. This will be + // the buffer used and the previous contents potentially copied into. if (somethingChanged && !inputFound) { const SdfPath aovId = GetAovPath(controllerId, localOutputs[i]); @@ -585,10 +681,21 @@ bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, _aovBufferIds.push_back(aovId); } } - if (colorInput.texture) + + // In case, we want to share the AOV buffers between frame passes but they are from different + // render delegates, we then need to copy the AOV to visualize. But be careful that's not always + // the color one we visualize. + + // Color AOV always means color & depth AOVs (where depth is optional). + if (outputToVisualize == pxr::HdAovTokens->color && colorInput.texture) { PrepareBuffersFromInputs(colorInput, depthInput, colorDesc, controllerId); } + // But depth AOV only means depth AOV only. + else if (outputToVisualize == pxr::HdAovTokens->depth && depthInput.texture) + { + PrepareDepthOnlyFromInput(depthInput, depthDesc, controllerId); + } // Create the list of AOV bindings. // This section only fills the 3 vectors below: aovBindingsClear, aovBindingsNoClear, @@ -619,7 +726,7 @@ bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, aovBindingsClear[i].aovSettings = outputDescs[i].aovSettings; // Note, it would be better to just assign the output buffer here, but this breaks some - // unit tests that expect this to be null and do a pointer-as-string comparison if it is not + // unit tests that expect this to be null and do a pointer-as-string comparison if it is not // which is not easily fixable. HdRenderBuffer* outputBuffer = static_cast(_pRenderIndex->GetBprim( HdPrimTypeTokens->renderBuffer, aovBindingsClear[i].renderBufferId)); @@ -647,7 +754,7 @@ bool RenderBufferManager::Impl::SetRenderOutputs(TfTokenVector const& outputs, if (localOutputs.size() > 0) { - SetViewportRenderOutput(localOutputs[0], controllerId); + SetViewportRenderOutput(outputToVisualize, controllerId); } // NOTE: The viewport data plumbed to tasks unfortunately depends on whether aovs are being @@ -799,11 +906,37 @@ RenderBufferManager::RenderBufferManager( SdfPath const& taskManagerUid, HdRenderIndex* pRenderIndex, SyncDelegatePtr& syncDelegate) : _taskManagerUid(taskManagerUid), _pRenderIndex(pRenderIndex) { - _impl = std::make_unique(pRenderIndex, syncDelegate); + _impl = std::make_unique(_pRenderIndex, syncDelegate); } RenderBufferManager::~RenderBufferManager() {} +TfTokenVector RenderBufferManager::GetAllRendererAovs() +{ + return { HdAovTokens->color, HdAovTokens->depth, + HdAovTokens->primId, HdAovTokens->elementId, HdAovTokens->instanceId }; +} + +TfTokenVector RenderBufferManager::GetSupportedRendererAovs() const +{ + if (_pRenderIndex->IsBprimTypeSupported(HdPrimTypeTokens->renderBuffer)) + { + auto const& candidates = GetAllRendererAovs(); + + TfTokenVector aovs; + for (auto const& aov : candidates) + { + if (_pRenderIndex->GetRenderDelegate()->GetDefaultAovDescriptor(aov).format != + HdFormatInvalid) + { + aovs.push_back(aov); + } + } + return aovs; + } + return {}; +} + HgiTextureHandle RenderBufferManager::GetAovTexture(TfToken const& token, HdEngine* engine) const { VtValue aov; @@ -869,10 +1002,15 @@ void RenderBufferManager::SetRenderOutputClearColor(const TfToken& name, const V _impl->SetRenderOutputClearColor(name, _taskManagerUid, clearValue); } -bool RenderBufferManager::SetRenderOutputs( - TfTokenVector const& names, RenderBufferBindings const& inputs, GfVec4d const& viewport) +bool RenderBufferManager::SetRenderOutputs(TfToken const& visualizeAOV, + TfTokenVector const& outputs, RenderBufferBindings const& inputs, GfVec4d const& viewport) { - return _impl->SetRenderOutputs(names, inputs, viewport, _taskManagerUid); + return _impl->SetRenderOutputs(visualizeAOV, outputs, inputs, viewport, _taskManagerUid); +} + +TfTokenVector const& RenderBufferManager::GetRenderOutputs() const +{ + return _impl->GetRenderOutputs(); } void RenderBufferManager::SetPresentationOutput(TfToken const& api, VtValue const& framebuffer) diff --git a/test/tests/composeTaskHelpers.cpp b/test/tests/composeTaskHelpers.cpp index 6138d019..3bedde4f 100644 --- a/test/tests/composeTaskHelpers.cpp +++ b/test/tests/composeTaskHelpers.cpp @@ -43,7 +43,8 @@ void AddComposeTask( // Create the fnCommit for the compose task. auto fnCommit = [&](hvt::TaskManager::GetTaskValueFn const& fnGetValue, - hvt::TaskManager::SetTaskValueFn const& fnSetValue) { + hvt::TaskManager::SetTaskValueFn const& fnSetValue) + { const VtValue value = fnGetValue(HdTokens->params); hvt::ComposeTaskParams params = value.Get(); @@ -67,14 +68,13 @@ void AddComposeTask( } // Renders the first frame pass i.e., do not display it and let the next frame pass doing it. -void RenderFirstFramePass(TestHelpers::FramePassInstance& framePass1, int width, int height, +void RenderFirstFramePass(TestHelpers::FramePassInstance& framePass, int width, int height, TestHelpers::TestStage const& stage) { - hvt::FramePassParams& params = framePass1.sceneFramePass->params(); + hvt::FramePassParams& params = framePass.sceneFramePass->params(); params.renderBufferSize = GfVec2i(width, height); - params.viewInfo.framing = - hvt::ViewParams::GetDefaultFraming(width, height); + params.viewInfo.framing = hvt::ViewParams::GetDefaultFraming(width, height); params.viewInfo.viewMatrix = stage.viewMatrix(); params.viewInfo.projectionMatrix = stage.projectionMatrix(); @@ -82,29 +82,33 @@ void RenderFirstFramePass(TestHelpers::FramePassInstance& framePass1, int width, params.viewInfo.material = stage.defaultMaterial(); params.viewInfo.ambient = stage.defaultAmbient(); - params.colorspace = HdxColorCorrectionTokens->disabled; - params.backgroundColor = TestHelpers::ColorDarkGrey; - params.selectionColor = TestHelpers::ColorYellow; + params.colorspace = HdxColorCorrectionTokens->disabled; + params.selectionColor = TestHelpers::ColorYellow; + + params.clearBackgroundColor = true; + params.backgroundColor = TestHelpers::ColorDarkGrey; + + params.clearBackgroundDepth = true; + params.backgroundDepth = 1.0f; // Delays the display to the next frame pass. params.enablePresentation = false; // Renders the frame pass. - framePass1.sceneFramePass->Render(); + framePass.sceneFramePass->Render(); } // Renders the second frame pass which also display the result. -void RenderSecondFramePass(TestHelpers::FramePassInstance& framePass2, int width, - int height, bool enablePresentTask, TestHelpers::TestStage const& stage, - hvt::RenderBufferBindings const& inputAOVs, - TestHelpers::RenderingBackend /*renderingBackend*/, - bool clearBackground /*= true*/) +void RenderSecondFramePass(TestHelpers::FramePassInstance& framePass, int width, int height, + bool enablePresentTask, TestHelpers::TestStage const& stage, + hvt::RenderBufferBindings const& inputAOVs, bool clearColorBackground, + pxr::GfVec4f const& colorBackground, bool clearDepthBackground) { - auto& params = framePass2.sceneFramePass->params(); + auto& pass = framePass.sceneFramePass; + auto& params = pass->params(); params.renderBufferSize = GfVec2i(width, height); - params.viewInfo.framing = - hvt::ViewParams::GetDefaultFraming(width, height); + params.viewInfo.framing = hvt::ViewParams::GetDefaultFraming(width, height); params.viewInfo.viewMatrix = stage.viewMatrix(); params.viewInfo.projectionMatrix = stage.projectionMatrix(); @@ -112,19 +116,22 @@ void RenderSecondFramePass(TestHelpers::FramePassInstance& framePass2, int width params.viewInfo.material = stage.defaultMaterial(); params.viewInfo.ambient = stage.defaultAmbient(); - params.colorspace = HdxColorCorrectionTokens->disabled; - params.clearBackgroundColor = clearBackground; - // NoAlpha is mandatory for the alpha blending. - params.backgroundColor = TestHelpers::ColorBlackNoAlpha; - params.selectionColor = TestHelpers::ColorYellow; + params.colorspace = HdxColorCorrectionTokens->disabled; + params.selectionColor = TestHelpers::ColorYellow; + + params.clearBackgroundColor = clearColorBackground; + params.backgroundColor = colorBackground; + + params.clearBackgroundDepth = clearDepthBackground; + params.backgroundDepth = 1.0f; params.enablePresentation = enablePresentTask; // Gets the list of tasks to render but use the render buffers from the first frame // pass. - const HdTaskSharedPtrVector renderTasks = framePass2.sceneFramePass->GetRenderTasks(inputAOVs); + const HdTaskSharedPtrVector renderTasks = pass->GetRenderTasks(inputAOVs); - framePass2.sceneFramePass->Render(renderTasks); + pass->Render(renderTasks); } -} // namespace TestHelpers \ No newline at end of file +} // namespace TestHelpers diff --git a/test/tests/composeTaskHelpers.h b/test/tests/composeTaskHelpers.h index aad09b0d..cbebff9b 100644 --- a/test/tests/composeTaskHelpers.h +++ b/test/tests/composeTaskHelpers.h @@ -29,13 +29,13 @@ void AddComposeTask( TestHelpers::FramePassInstance const& framePass1, TestHelpers::FramePassInstance& framePass2); // Renders the first frame pass i.e., do not display it and let the next frame pass doing it. -void RenderFirstFramePass(TestHelpers::FramePassInstance& framePass1, int width, int height, +void RenderFirstFramePass(TestHelpers::FramePassInstance& framePass, int width, int height, TestHelpers::TestStage const& stage); // Renders the second frame pass which also display the result. -void RenderSecondFramePass(TestHelpers::FramePassInstance& framePass2, int width, int height, +void RenderSecondFramePass(TestHelpers::FramePassInstance& framePass, int width, int height, bool enablePresentTask, TestHelpers::TestStage const& stage, - hvt::RenderBufferBindings const& inputAOVs, TestHelpers::RenderingBackend renderingBackend, - bool clearBackground = true); + hvt::RenderBufferBindings const& inputAOVs, bool clearColorBackground, + pxr::GfVec4f const& colorBackground, bool clearDepthBackground); } // namespace TestHelpers diff --git a/test/tests/testComposeTask.cpp b/test/tests/testComposeTask.cpp index 7f8259e2..8efdd8e2 100644 --- a/test/tests/testComposeTask.cpp +++ b/test/tests/testComposeTask.cpp @@ -79,7 +79,8 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask) // Render 10 times (i.e., arbitrary number to guaranty best result). int frameCount = 10; - auto render = [&]() { + auto render = [&]() + { TestHelpers::RenderFirstFramePass(framePass1, context->width(), context->height(), stage); // Force GPU sync. Wait for all GPU commands to complete before proceeding. @@ -90,18 +91,18 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask) // Gets the depth from the first frame pass and use it so the overlays draw into the same // depth buffer (because depth has always the same bit depth i.e., 32-bit float for all // the render delegates). - auto& pass = framePass1.sceneFramePass; - hvt::RenderBufferBindings inputAOVs = pass->GetRenderBufferBindingsForNextPass( - { pxr::HdAovTokens->depth }); + auto& pass = framePass1.sceneFramePass; + hvt::RenderBufferBindings inputAOVs = + pass->GetRenderBufferBindingsForNextPass({ pxr::HdAovTokens->depth }); { hvt::FramePassParams& params = framePass2.sceneFramePass->params(); - params.renderBufferSize = GfVec2i(context->width(), context->height()); + params.renderBufferSize = GfVec2i(context->width(), context->height()); params.viewInfo.framing = hvt::ViewParams::GetDefaultFraming(context->width(), context->height()); - params.viewInfo.viewMatrix = stage.viewMatrix(); + params.viewInfo.viewMatrix = stage.viewMatrix(); params.viewInfo.projectionMatrix = stage.projectionMatrix(); params.viewInfo.lights = stage.defaultLights(); params.viewInfo.material = stage.defaultMaterial(); @@ -174,7 +175,8 @@ HVT_TEST(TestViewportToolbox, compose_ShareTextures) // Render 10 times (i.e., arbitrary number to guaranty best result). int frameCount = 10; - auto render = [&]() { + auto render = [&]() + { TestHelpers::RenderFirstFramePass(framePass1, context->width(), context->height(), stage); // Force GPU sync. Wait for all GPU commands to complete before proceeding. @@ -192,11 +194,11 @@ HVT_TEST(TestViewportToolbox, compose_ShareTextures) { hvt::FramePassParams& params = framePass2.sceneFramePass->params(); - params.renderBufferSize = GfVec2i(context->width(), context->height()); + params.renderBufferSize = GfVec2i(context->width(), context->height()); params.viewInfo.framing = hvt::ViewParams::GetDefaultFraming(context->width(), context->height()); - params.viewInfo.viewMatrix = stage.viewMatrix(); + params.viewInfo.viewMatrix = stage.viewMatrix(); params.viewInfo.projectionMatrix = stage.projectionMatrix(); params.viewInfo.lights = stage.defaultLights(); params.viewInfo.material = stage.defaultMaterial(); @@ -245,8 +247,8 @@ HVT_TEST(TestViewportToolbox, compose_ShareTextures) // Note: The 'Bounding Box' is used (instead of the 'WireFrame' one) because it works on all // desktop platforms. -// Disabled for iOS as the result is not stable. Refer to OGSMOD-7344 -// Disabled for Android due to baseline inconsistancy between runners. Refer to OGSMOD-8067 +// OGSMOD-7344: Disabled for iOS as the result is not stable. +// OGSMOD-8067: Disabled for Android due to baseline inconsistency between runs. #if TARGET_OS_IPHONE == 1 || defined(__ANDROID__) HVT_TEST(TestViewportToolbox, DISABLED_compose_ComposeTask2) #else @@ -255,7 +257,7 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask2) { // This unit test uses the 'Storm' render delegate for the two frame passes, to demonstrate that // the compose task works. But the first frame pass displays the bounding box of the model and - // the second one displays the model + // the second one displays the model. auto context = TestHelpers::CreateTestContext(); @@ -295,16 +297,17 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask2) // Defines the second frame pass using the Storm render delegate. framePass2 = TestHelpers::FramePassInstance::CreateInstance( - "HdStormRendererPlugin", stage.stage(), context->_backend); + "HdStormRendererPlugin", stage.stage(), context->_backend, "/sceneFramePass2"); - // Adds the 'Compose' task to the second frame pass. + // Adds the 'Compose' task to the second frame pass i.e., compose the color AOV. TestHelpers::AddComposeTask(framePass1, framePass2); // Render 10 times (i.e., arbitrary number to guarantee best result). int frameCount = 10; - auto render = [&]() { + auto render = [&]() + { TestHelpers::RenderFirstFramePass(framePass1, context->width(), context->height(), stage); // Force GPU sync. Wait for all GPU commands to complete before proceeding. @@ -316,12 +319,14 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask2) // depth buffer (because depth has always the same bit depth i.e., 32-bit float for all // the render delegates). - auto& pass = framePass1.sceneFramePass; + // No need to share the color AOV as the ComposeTask will take care of it. hvt::RenderBufferBindings inputAOVs = - pass->GetRenderBufferBindingsForNextPass({ pxr::HdAovTokens->depth }); - TestHelpers::RenderSecondFramePass( - framePass2, context->width(), context->height(), context->presentationEnabled(), - stage, inputAOVs, GetParam()); + framePass1.sceneFramePass->GetRenderBufferBindingsForNextPass({ pxr::HdAovTokens->depth }); + + // NoAlpha mandatory for the blending used by ComposeTask. + TestHelpers::RenderSecondFramePass(framePass2, context->width(), context->height(), + context->presentationEnabled(), stage, inputAOVs, true, TestHelpers::ColorBlackNoAlpha, + false); return --frameCount > 0; }; @@ -336,8 +341,8 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask2) ASSERT_TRUE(context->validateImages(computedImagePath, TestHelpers::gTestNames.fixtureName)); } -// Disabled for iOS as the result is not stable. Refer to OGSMOD-7344 -// Disabled for Android due to baseline inconsistancy between runners. Refer to OGSMOD-8067 +// OGSMOD-7344: Disabled for iOS as the result is not stable. +// OGSMOD-8067: Disabled for Android due to baseline inconsistency between runs. #if TARGET_OS_IPHONE == 1 || defined(__ANDROID__) HVT_TEST(TestViewportToolbox, DISABLED_compose_ComposeTask3) #else @@ -387,7 +392,7 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask3) passDesc.uid = SdfPath("/sceneFramePass2"); framePass2.sceneFramePass = hvt::ViewportEngine::CreateFramePass(passDesc); - // Adds the 'Compose' task to the second frame pass. + // Adds the 'Compose' task to the second frame pass i.e., compose the color AOV. TestHelpers::AddComposeTask(framePass1, framePass2); } @@ -395,7 +400,8 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask3) // Render 10 times (i.e., arbitrary number to guarantee best result). int frameCount = 10; - auto render = [&]() { + auto render = [&]() + { TestHelpers::RenderFirstFramePass(framePass1, context->width(), context->height(), stage); // Force GPU sync. Wait for all GPU commands to complete before proceeding. @@ -407,13 +413,14 @@ HVT_TEST(TestViewportToolbox, compose_ComposeTask3) // depth buffer (because depth has always the same bit depth i.e., 32-bit float for all // the render delegates). - auto& pass = framePass1.sceneFramePass; - hvt::RenderBufferBindings inputAOVs = pass->GetRenderBufferBindingsForNextPass( - { pxr::HdAovTokens->depth }); + // No need to share the color AOV as the ComposeTask will take care of it. + hvt::RenderBufferBindings inputAOVs = + framePass1.sceneFramePass->GetRenderBufferBindingsForNextPass({ pxr::HdAovTokens->depth }); - TestHelpers::RenderSecondFramePass( - framePass2, context->width(), context->height(), context->presentationEnabled(), - stage, inputAOVs, GetParam()); + // NoAlpha mandatory for the blending used by ComposeTask. + TestHelpers::RenderSecondFramePass(framePass2, context->width(), context->height(), + context->presentationEnabled(), stage, inputAOVs, true, TestHelpers::ColorBlackNoAlpha, + false); return --frameCount > 0; }; @@ -481,7 +488,8 @@ HVT_TEST(TestViewportToolbox, compose_ShareTextures4) // Render 10 times (i.e., arbitrary number to guarantee best result). int frameCount = 10; - auto render = [&]() { + auto render = [&]() + { TestHelpers::RenderFirstFramePass(framePass1, context->width(), context->height(), stage); // Force GPU sync. Wait for all GPU commands to complete before proceeding. @@ -498,9 +506,8 @@ HVT_TEST(TestViewportToolbox, compose_ShareTextures4) // When sharing the render buffers, do not clear the background as it contains the rendering // result of the previous frame pass. - TestHelpers::RenderSecondFramePass( - framePass2, context->width(), context->height(), context->presentationEnabled(), - stage, inputAOVs, GetParam(), false); + TestHelpers::RenderSecondFramePass(framePass2, context->width(), context->height(), + context->presentationEnabled(), stage, inputAOVs, false, TestHelpers::ColorDarkGrey, false); return --frameCount > 0; }; diff --git a/test/tests/testFramePass.cpp b/test/tests/testFramePass.cpp index 0c3cf639..d8f39932 100644 --- a/test/tests/testFramePass.cpp +++ b/test/tests/testFramePass.cpp @@ -464,4 +464,75 @@ HVT_TEST(TestViewportToolbox, TestFramePassSelectionSettingsProvider) EXPECT_TRUE(clearedLocatePaths.empty()); // Clean up. - FramePass destructor will handle cleanup -} \ No newline at end of file +} + +// The test is independent from the backend. +TEST(TestViewportToolbox, TestFramePassAOVs) +{ + // The goal of this unit test is to validate that the FramePass correctly provides + // access to AOVs and that the AOVs are properly set. + + auto testContext = TestHelpers::CreateTestContext(); + + TestHelpers::TestStage stage(testContext->_backend); + ASSERT_TRUE(stage.open(testContext->_sceneFilepath)); + + hvt::RenderIndexProxyPtr renderIndexProxy; + hvt::FramePassPtr framePass; + + { + // Create the render index. + hvt::RendererDescriptor rendererDesc; + rendererDesc.hgiDriver = &testContext->_backend->hgiDriver(); + rendererDesc.rendererName = "HdStormRendererPlugin"; + hvt::ViewportEngine::CreateRenderer(renderIndexProxy, rendererDesc); + + // Create a FramePass which internally creates a SelectionHelper. + // (SelectionSettingsProvider) + static const SdfPath framePassId("/sceneFramePass"); + hvt::FramePassDescriptor desc { renderIndexProxy->RenderIndex(), framePassId, {} }; + framePass = hvt::ViewportEngine::CreateFramePass(desc); + } + + auto& params = framePass->params(); + + // Partial initialization of the FramePass parameters. + const GfVec2i renderSize(testContext->width(), testContext->height()); + params.renderBufferSize = renderSize; + params.viewInfo.framing = hvt::ViewParams::GetDefaultFraming(renderSize[0], renderSize[1]); + + // The caller knows what AOVs to render. + params = framePass->params(); + params.renderOutputs = { HdAovTokens->color, HdAovTokens->depth }; + framePass->Render(); + + // Verify the AOVs are properly set. + auto aovs = framePass->GetRenderBufferManager()->GetRenderOutputs(); + ASSERT_EQ(aovs.size(), 2); + ASSERT_EQ(aovs[0], HdAovTokens->color); + ASSERT_EQ(aovs[1], HdAovTokens->depth); + + // The caller doesn't know what AOVs to render, so it's expected that the default AOVs are + // rendered. + params = framePass->params(); + params.renderOutputs = {}; + framePass->Render(); + + // Verify the AOVs are properly set. + aovs = framePass->GetRenderBufferManager()->GetRenderOutputs(); + ASSERT_EQ(aovs.size(), 2); + ASSERT_EQ(aovs[0], HdAovTokens->color); + ASSERT_EQ(aovs[1], HdAovTokens->depth); + + // The caller doesn't know what AOVs to render so it's expected that the default AOVs are + // rendered. However, the visualizeAOV is set to depth, so only the depth AOV is rendered. + params = framePass->params(); + params.renderOutputs = {}; + params.visualizeAOV = HdAovTokens->depth; + framePass->Render(); + + // Verify the AOVs are properly set. + aovs = framePass->GetRenderBufferManager()->GetRenderOutputs(); + ASSERT_EQ(aovs.size(), 1); + ASSERT_EQ(aovs[0], HdAovTokens->depth); +}