From 846e2736a535f0c50348b66ff065affc370cdbc0 Mon Sep 17 00:00:00 2001 From: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> Date: Thu, 17 Feb 2022 08:50:50 -0800 Subject: [PATCH] [new] ATOM-17253 Using AtomTressFX gem as an example to inject hair passes to main pipeline at run-time (#7661) * Change AtomTressFX passes to write to exsiting DepthLinear buffer instead of creating a new one as output. Signed-off-by: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> * Added new api in RPI to apply render pipeline changes from FP Signed-off-by: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> * Update AtomTressFX gem to create hair parent pass at runtime Signed-off-by: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> * Change Scene::ApplyRenderPipelineChange() to TryApplyRenderPIpelineChanges(). Have TryApplyRenderPIpelineChanges() called automatically when a render pipeline is added to a Scene. Re-apply the render pipeline change when RenderPipeline got recreated (Pass hot-reloading support) Add AddPassBefore() and AddPassAfter() function to RenderPIpeline class. Signed-off-by: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> --- AutomatedTesting/Passes/MainPipeline.pass | 121 ++---------------- Code/Editor/CryEdit.cpp | 4 + .../Assets/Passes/MainRenderPipeline.azasset | 1 + .../Atom/RPI.Public/FeatureProcessor.h | 5 + .../Include/Atom/RPI.Public/Pass/ParentPass.h | 1 + .../Include/Atom/RPI.Public/RenderPipeline.h | 29 ++++- .../RPI/Code/Include/Atom/RPI.Public/Scene.h | 4 + .../System/RenderPipelineDescriptor.h | 3 + .../Source/RPI.Public/Pass/ParentPass.cpp | 14 +- .../Source/RPI.Public/Pass/PassLibrary.cpp | 2 +- .../RPI/Code/Source/RPI.Public/RPISystem.cpp | 2 + .../Code/Source/RPI.Public/RenderPipeline.cpp | 67 +++++++++- .../Atom/RPI/Code/Source/RPI.Public/Scene.cpp | 19 +++ .../System/RenderPipelineDescriptor.cpp | 1 + .../Passes/AtomTressFX_PassRequest.azasset | 96 ++++++++++++++ .../Passes/AtomTressFX_PassTemplates.azasset | 4 + .../Assets/Passes/HairDepthToLinear.pass | 38 ++++++ .../Assets/Passes/HairParentPass.pass | 38 ++---- .../Assets/Passes/HairParentShortCutPass.pass | 35 ++--- .../Assets/Passes/HairResolvePPLL.pass | 1 + Gems/AtomTressFX/Assets/seedList.seed | 76 +---------- .../Code/Rendering/HairFeatureProcessor.cpp | 74 +++++++++-- .../Code/Rendering/HairFeatureProcessor.h | 7 +- 23 files changed, 388 insertions(+), 254 deletions(-) create mode 100644 Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassRequest.azasset create mode 100644 Gems/AtomTressFX/Assets/Passes/HairDepthToLinear.pass diff --git a/AutomatedTesting/Passes/MainPipeline.pass b/AutomatedTesting/Passes/MainPipeline.pass index 8ad4006570..15edec8e4d 100644 --- a/AutomatedTesting/Passes/MainPipeline.pass +++ b/AutomatedTesting/Passes/MainPipeline.pass @@ -215,99 +215,6 @@ } ] }, - - { - // NOTE: HairParentPass does not write into Depth MSAA from Opaque Pass. If new passes downstream - // of HairParentPass will need to use Depth MSAA, HairParentPass will need to be updated to use Depth MSAA - // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated - // .azsl file will need to be updated. - "Name": "HairParentPass", - // Note: The following two lines represent the choice of rendering pipeline for the hair. - // You can either choose to use PPLL or ShortCut and accordingly change the flag - // 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp' - // "TemplateName": "HairParentPassTemplate", - "TemplateName": "HairParentShortCutPassTemplate", - "Enabled": true, - "Connections": [ - // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. - // If DepthLinear is not available - connect to another viewport (non MSAA) image. - { - "LocalSlot": "DepthLinearInput", - "AttachmentRef": { - "Pass": "DepthPrePass", - "Attachment": "DepthLinear" - } - }, - { - "LocalSlot": "Depth", - "AttachmentRef": { - "Pass": "DepthPrePass", - "Attachment": "Depth" - } - }, - { - "LocalSlot": "RenderTargetInputOutput", - "AttachmentRef": { - "Pass": "OpaquePass", - "Attachment": "Output" - } - }, - { - "LocalSlot": "RenderTargetInputOnly", - "AttachmentRef": { - "Pass": "OpaquePass", - "Attachment": "Output" - } - }, - - // Shadows resources - { - "LocalSlot": "DirectionalShadowmap", - "AttachmentRef": { - "Pass": "ShadowPass", - "Attachment": "DirectionalShadowmap" - } - }, - { - "LocalSlot": "DirectionalESM", - "AttachmentRef": { - "Pass": "ShadowPass", - "Attachment": "DirectionalESM" - } - }, - { - "LocalSlot": "ProjectedShadowmap", - "AttachmentRef": { - "Pass": "ShadowPass", - "Attachment": "ProjectedShadowmap" - } - }, - { - "LocalSlot": "ProjectedESM", - "AttachmentRef": { - "Pass": "ShadowPass", - "Attachment": "ProjectedESM" - } - }, - - // Lighting Resources - { - "LocalSlot": "TileLightData", - "AttachmentRef": { - "Pass": "LightCullingPass", - "Attachment": "TileLightData" - } - }, - { - "LocalSlot": "LightListRemapped", - "AttachmentRef": { - "Pass": "LightCullingPass", - "Attachment": "LightListRemapped" - } - } - ] - }, - { "Name": "TransparentPass", "TemplateName": "TransparentParentTemplate", @@ -357,22 +264,22 @@ { "LocalSlot": "InputLinearDepth", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "DepthLinear" } }, { "LocalSlot": "DepthStencil", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "Depth" } }, { "LocalSlot": "InputOutput", "AttachmentRef": { - "Pass": "HairParentPass", - "Attachment": "RenderTargetInputOutput" + "Pass": "OpaquePass", + "Attachment": "Output" } } ] @@ -385,22 +292,22 @@ { "LocalSlot": "InputLinearDepth", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "DepthLinear" } }, { "LocalSlot": "InputDepthStencil", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "Depth" } }, { "LocalSlot": "RenderTargetInputOutput", "AttachmentRef": { - "Pass": "HairParentPass", - "Attachment": "RenderTargetInputOutput" + "Pass": "TransparentPass", + "Attachment": "InputOutput" } } ], @@ -440,7 +347,7 @@ { "LocalSlot": "Depth", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "Depth" } }, @@ -502,7 +409,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "Depth" } } @@ -561,7 +468,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "Depth" } } @@ -574,14 +481,14 @@ { "LocalSlot": "InputOutput", "AttachmentRef": { - "Pass": "LyShinePass", - "Attachment": "ColorInputOutput" + "Pass": "DebugOverlayPass", + "Attachment": "InputOutput" } }, { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "HairParentPass", + "Pass": "DepthPrePass", "Attachment": "Depth" } } diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index f3c38fdef7..db4a0cbbbd 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -1340,6 +1340,8 @@ void CCryEditApp::CompileCriticalAssets() const } assetsInQueueNotifcation.BusDisconnect(); + AZ_TracePrintf("Editor", "CriticalAssetsCompiled\n"); + // Signal the "CriticalAssetsCompiled" lifecycle event // Also reload the "assetcatalog.xml" if it exists if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) @@ -1411,11 +1413,13 @@ bool CCryEditApp::ConnectToAssetProcessor() const if (connectedToAssetProcessor) { + AZ_TracePrintf("Editor", "Connected to Asset Processor\n"); CCryEditApp::OutputStartupMessage(QString("Connected to Asset Processor")); CompileCriticalAssets(); return true; } + AZ_TracePrintf("Editor", "Failed to connect to Asset Processor\n"); CCryEditApp::OutputStartupMessage(QString("Failed to connect to Asset Processor")); return false; } diff --git a/Gems/Atom/Feature/Common/Assets/Passes/MainRenderPipeline.azasset b/Gems/Atom/Feature/Common/Assets/Passes/MainRenderPipeline.azasset index bc12f61a8f..71ebb87b4f 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/MainRenderPipeline.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/MainRenderPipeline.azasset @@ -6,6 +6,7 @@ "Name": "MainPipeline", "MainViewTag": "MainCamera", "RootPassTemplate": "MainPipeline", + "AllowModification": true, "RenderSettings": { "MultisampleState": { "samples": 4 diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/FeatureProcessor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/FeatureProcessor.h index caf3728c89..9d079ef6dc 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/FeatureProcessor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/FeatureProcessor.h @@ -30,6 +30,8 @@ namespace AZ namespace RPI { + class RenderPipeline; + //! @class FeatureProcessor //! @brief Interface that FeatureProcessors should derive from //! @detail FeatureProcceors will record simulation state from the simulation job graph into a buffer that is isolated from the asynchronous rendering graph. @@ -87,6 +89,9 @@ namespace AZ //! Perform any necessary deactivation. virtual void Deactivate() {} + //! Apply changes and add additional render passes to the render pipeline from the feature processors + virtual void ApplyRenderPipelineChange(RenderPipeline* ) {} + //! Allows the feature processor to expose supporting views based on //! the main views passed in. Main views (persistent views) are views that must be //! rendered and impacts the presentation of the application. Support diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h index e739547255..a79ffbfa6f 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h @@ -66,6 +66,7 @@ namespace AZ //! Inserts a pass at specified position //! If the position is invalid, the child pass won't be added, and the function returns false bool InsertChild(const Ptr& child, ChildPassIndex position); + bool InsertChild(const Ptr& child, uint32_t index); //! Searches for a child pass with the given name. Returns the child's index if found, null index otherwise ChildPassIndex FindChildPassIndex(const Name& passName) const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h index 6cba0c774f..09c4e45d9f 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h @@ -184,6 +184,23 @@ namespace AZ //! Get draw filter mask RHI::DrawFilterMask GetDrawFilterMask() const; + //! Get the RenderPipelineDescriptor which was used to create this RenderPipeline + const RenderPipelineDescriptor& GetDescriptor() const; + + // Helper functions to modify the passes of this render pipeline + //! Find a reference pass's location and add the new pass before the reference pass + //! After the new pass was inserted, the new pass and the reference pass are siblings + bool AddPassBefore(Ptr newPass, const AZ::Name& referencePassName); + + //! Find a reference pass's location and add the new pass after the reference pass + //! After the new pass was inserted, the new pass and the reference pass are siblings + bool AddPassAfter(Ptr newPass, const AZ::Name& referencePassName); + + //! Find the first pass with matching name in the render pipeline + //! Note: to find all the passes with matching name in this render pipeline, + //! use RPI::PassSystemInterface::Get()->ForEachPass() function instead. + Ptr FindFirstPass(const AZ::Name& passName); + private: RenderPipeline() = default; @@ -238,8 +255,8 @@ namespace AZ PipelineViewTag m_mainViewTag; - // Whether the pipeline should be removed after one execution - bool m_executeOnce = false; + // Was the pipeline modified by Scene's feature processor + bool m_wasModifiedByScene = false; // The window handle associated with this render pipeline if it's created for a window AzFramework::NativeWindowHandle m_windowHandle = nullptr; @@ -247,15 +264,15 @@ namespace AZ // Render settings that can be queried by passes to setup things like render target resolution PipelineRenderSettings m_activeRenderSettings; - // Original settings from RenderPipelineDescriptor, used to revert active render settings to original settings from RenderPipelineDescriptor - PipelineRenderSettings m_originalRenderSettings; - // A tag to filter draw items submitted by passes of this render pipeline. // This tag is allocated when it's added to a scene. It's set to invalid when it's removed to the scene. RHI::DrawFilterTag m_drawFilterTag; // A mask to filter draw items submitted by passes of this render pipeline. // This mask is created from the value of m_drawFilterTag. - RHI::DrawFilterMask m_drawFilterMask = 0; + RHI::DrawFilterMask m_drawFilterMask = 0; + + // The descriptor used to created this render pipeline + RenderPipelineDescriptor m_descriptor; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h index 0d41e0b5e9..c61716f253 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h @@ -169,6 +169,9 @@ namespace AZ //! This function is called every time scene's render pipelines change. //! User may call this function explicitly if render pipelines were changed void RebuildPipelineStatesLookup(); + + //! Try apply render pipeline changes from each feature processors if the pipeline allows modification and wasn't modified. + void TryApplyRenderPipelineChanges(RenderPipeline* pipeline); protected: // SceneFinder overrides... @@ -192,6 +195,7 @@ namespace AZ // This is called after PassSystem's FramePrepare so passes can still modify view srgs in its FramePrepareIntenal function before they are submitted to command list void UpdateSrgs(); + private: Scene(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/RenderPipelineDescriptor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/RenderPipelineDescriptor.h index d330617e9d..a4bfe90966 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/RenderPipelineDescriptor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/RenderPipelineDescriptor.h @@ -41,6 +41,9 @@ namespace AZ //! Flag indicating if this pipeline should execute one time and then be removed. bool m_executeOnce = false; + + //! Flag indicating if this pipeline can accept modifications from scene's feature processors + bool m_allowModification = false; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp index 3933649f13..fae06ae021 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/ParentPass.cpp @@ -76,6 +76,16 @@ namespace AZ } bool ParentPass::InsertChild(const Ptr& child, ChildPassIndex position) + { + if (!position.IsValid()) + { + AZ_Assert(false, "Can't insert a child pass with invalid position"); + return false; + } + return InsertChild(child, position.GetIndex()); + } + + bool ParentPass::InsertChild(const Ptr& child, uint32_t index) { if (child->m_parent != nullptr) { @@ -83,13 +93,13 @@ namespace AZ return false; } - if (!position.IsValid() || position.GetIndex() > m_children.size()) + if (index > m_children.size()) { AZ_Assert(false, "Can't insert a child pass with invalid position"); return false; } - auto insertPos = m_children.cbegin() + position.GetIndex(); + auto insertPos = m_children.cbegin() + index; m_children.insert(insertPos, child); child->m_parent = this; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp index 7ea3706d17..a2f055fb39 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp @@ -294,7 +294,7 @@ namespace AZ void PassLibrary::OnAssetReloaded(Data::Asset asset) { // Handle pass asset reload - Data::Asset passAsset = Data::static_pointer_cast(asset); + Data::Asset passAsset = { asset.GetAs() , AZ::Data::AssetLoadBehavior::PreLoad}; if (passAsset && passAsset->GetPassTemplate()) { LoadPassAsset(passAsset->GetPassTemplate()->m_name, passAsset, true); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp index 57bef2c7e5..485280a47b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp @@ -370,6 +370,7 @@ namespace AZ m_commonShaderAssetForSrgs = AssetUtils::LoadCriticalAsset( m_descriptor.m_commonSrgsShaderAssetPath.c_str()); if (!m_commonShaderAssetForSrgs.IsReady()) { + AZ_Error("RPI system", false, "Failed to load RPI system asset %s", m_descriptor.m_commonSrgsShaderAssetPath.c_str()); return; } m_sceneSrgLayout = m_commonShaderAssetForSrgs->FindShaderResourceGroupLayout(SrgBindingSlot::Scene); @@ -395,6 +396,7 @@ namespace AZ m_passSystem.InitPassTemplates(); m_systemAssetsInitialized = true; + AZ_TracePrintf("RPI system", "System assets initialized\n"); } bool RPISystem::IsInitialized() const diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp index b42d065017..806d6a3749 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -97,10 +98,9 @@ namespace AZ void RenderPipeline::InitializeRenderPipeline(RenderPipeline* pipeline, const RenderPipelineDescriptor& desc) { + pipeline->m_descriptor = desc; pipeline->m_mainViewTag = Name(desc.m_mainViewTagName); pipeline->m_nameId = desc.m_name.data(); - pipeline->m_executeOnce = desc.m_executeOnce; - pipeline->m_originalRenderSettings = desc.m_renderSettings; pipeline->m_activeRenderSettings = desc.m_renderSettings; pipeline->m_rootPass->SetRenderPipeline(pipeline); pipeline->m_rootPass->m_flags.m_isPipelineRoot = true; @@ -337,6 +337,10 @@ namespace AZ m_rootPass = newRoot; passSystem->GetRootPass()->AddChild(m_rootPass); + // Re-Apply render pipeline change + m_wasModifiedByScene = false; + m_scene->TryApplyRenderPipelineChanges(this); + m_wasPassModified = true; } else @@ -366,7 +370,7 @@ namespace AZ bool RenderPipeline::IsExecuteOnce() { - return m_executeOnce; + return m_descriptor.m_executeOnce; } void RenderPipeline::RemoveFromScene() @@ -477,7 +481,7 @@ namespace AZ void RenderPipeline::RevertRenderSettings() { - m_activeRenderSettings = m_originalRenderSettings; + m_activeRenderSettings = m_descriptor.m_renderSettings; } void RenderPipeline::AddToRenderTickOnce() @@ -527,5 +531,60 @@ namespace AZ m_drawFilterMask = 0; } } + + const RenderPipelineDescriptor& RenderPipeline::GetDescriptor() const + { + return m_descriptor; + } + + bool RenderPipeline::AddPassBefore(Ptr newPass, const AZ::Name& referencePassName) + { + auto foundPass = FindFirstPass(referencePassName); + + if (!foundPass) + { + AZ_Warning("RenderPipeline", false, "Add pass to render pipeline failed: can't find reference pass [%s] in render pipeline [%s]", + referencePassName.GetCStr(), GetId().GetCStr()); + return false; + } + + // insert the pass + auto parentPass = foundPass->GetParent(); + auto passIndex = parentPass->FindChildPassIndex(referencePassName); + // Note: no need to check if passIndex is valid since the pass was already found + return parentPass->InsertChild(newPass, passIndex.GetIndex()); + } + + bool RenderPipeline::AddPassAfter(Ptr newPass, const AZ::Name& referencePassName) + { + auto foundPass = FindFirstPass(referencePassName); + + if (!foundPass) + { + AZ_Warning("RenderPipeline", false, "Add pass to render pipeline failed: can't find reference pass [%s] in render pipeline [%s]", + referencePassName.GetCStr(), GetId().GetCStr()); + return false; + } + + // insert the pass + auto parentPass = foundPass->GetParent(); + auto passIndex = parentPass->FindChildPassIndex(referencePassName); + // Note: no need to check if passIndex is valid since the pass was already found + return parentPass->InsertChild(newPass, passIndex.GetIndex()+1); + } + + Ptr RenderPipeline::FindFirstPass(const AZ::Name& passName) + { + auto passFilter = RPI::PassFilter::CreateWithPassHierarchy({passName}); + passFilter.SetOwnerRenderPipeline(this); + RPI::Ptr foundPass = nullptr; + RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [&foundPass](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow + { + foundPass = pass; + return RPI::PassFilterExecutionFlow::StopVisitingPasses; + }); + + return foundPass; + } } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index 83ac77a390..ac6210c1f2 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -285,6 +285,22 @@ namespace AZ return foundFP == AZStd::end(m_featureProcessors) ? nullptr : (*foundFP).get(); } + void Scene::TryApplyRenderPipelineChanges(RenderPipeline* pipeline) + { + // return directly if the pipeline doesn't allow modification or it was already modifed by scene + if (!pipeline->m_descriptor.m_allowModification || pipeline->m_wasModifiedByScene) + { + return; + } + + pipeline->m_wasModifiedByScene = true; + for (auto& fp : m_featureProcessors) + { + fp->ApplyRenderPipelineChange(pipeline); + } + AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges(); + } + void Scene::AddRenderPipeline(RenderPipelinePtr pipeline) { if (pipeline->m_scene != nullptr) @@ -311,6 +327,9 @@ namespace AZ } pipeline->OnAddedToScene(this); + + TryApplyRenderPipelineChanges(pipeline.get()); + PassSystemInterface::Get()->ProcessQueuedChanges(); pipeline->BuildPipelineViews(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/System/RenderPipelineDescriptor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/System/RenderPipelineDescriptor.cpp index d24204ab30..fa9c196395 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/System/RenderPipelineDescriptor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/System/RenderPipelineDescriptor.cpp @@ -25,6 +25,7 @@ namespace AZ ->Field("RootPassTemplate", &RenderPipelineDescriptor::m_rootPassTemplate) ->Field("ExecuteOnce", &RenderPipelineDescriptor::m_executeOnce) ->Field("RenderSettings", &RenderPipelineDescriptor::m_renderSettings) + ->Field("AllowModification", &RenderPipelineDescriptor::m_allowModification) ; } } diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassRequest.azasset b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassRequest.azasset new file mode 100644 index 0000000000..4a4f853f25 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassRequest.azasset @@ -0,0 +1,96 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassRequest", + "ClassData": { + // NOTE: HairParentPass does not write into Depth MSAA from Opaque Pass. If new passes downstream + // of HairParentPass will need to use Depth MSAA, HairParentPass will need to be updated to use Depth MSAA + // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated + // .azsl file will need to be updated. + "Name": "HairParentPass", + // Note: The following two lines represent the choice of rendering pipeline for the hair. + // You can either choose to use PPLL or ShortCut by changing TemplateName to HairParentPassTemplate (for PPLL) + // or HairParentShortCutPassTemplate (for Shortcut) + // "TemplateName": "HairParentPassTemplate", + "TemplateName": "HairParentShortCutPassTemplate", + "Enabled": true, + "Connections": [ + // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. + // If DepthLinear is not available - connect to another viewport (non MSAA) image. + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "RenderTargetInputOnly", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Output" + } + }, + + // Shadows resources + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + + // Lighting Resources + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset index 1580a4e1a9..82e3b34014 100644 --- a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset @@ -62,6 +62,10 @@ { "Name": "HairShortCutResolveColorPassTemplate", "Path": "Passes/HairShortCutResolveColor.pass" + }, + { + "Name": "HairDepthToLinearTemplate", + "Path": "Passes/HairDepthToLinear.pass" } ] } diff --git a/Gems/AtomTressFX/Assets/Passes/HairDepthToLinear.pass b/Gems/AtomTressFX/Assets/Passes/HairDepthToLinear.pass new file mode 100644 index 0000000000..2c71e7d559 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairDepthToLinear.pass @@ -0,0 +1,38 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairDepthToLinearTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + "Name": "Input", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "AspectFlags": [ + "Depth" + ] + } + }, + { + "Name": "Output", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "DontCare" + } + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/DepthToLinearDepth.shader" + }, + "PipelineViewTag": "MainCamera" + } + } + } +} diff --git a/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass b/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass index c8c90e24f7..83f7dec3e4 100644 --- a/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass +++ b/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass @@ -25,17 +25,11 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "DepthStencil" }, - // Keep DepthLinear as input - used to set the size of the Head PPLL image buffer. - // If DepthLinear is not availbale - connect to another viewport (non MSAA) image. - { - "Name": "DepthLinearInput", - "SlotType": "Input" - }, + // used to set the size of the Head PPLL image buffer. { "Name": "DepthLinear", - "SlotType": "Output" + "SlotType": "InputOutput" }, - // Lights & Shadows resources { "Name": "DirectionalShadowmap", @@ -62,21 +56,6 @@ "SlotType": "Input" } ], - "Connections": [ - { - "LocalSlot": "DepthLinear", - "AttachmentRef": { - "Pass": "DepthToDepthLinearPass", - "Attachment": "Output" - } - } - ], - "FallbackConnections": [ - { - "Input": "DepthLinearInput", - "Output": "DepthLinear" - } - ], "PassRequests": [ { "Name": "HairGlobalShapeConstraintsComputePass", @@ -172,7 +151,7 @@ "LocalSlot": "DepthLinear", "AttachmentRef": { "Pass": "Parent", - "Attachment": "DepthLinearInput" + "Attachment": "DepthLinear" } }, { @@ -260,7 +239,7 @@ "LocalSlot": "DepthLinear", "AttachmentRef": { "Pass": "Parent", - "Attachment": "DepthLinearInput" + "Attachment": "DepthLinear" } }, @@ -334,7 +313,7 @@ // buffer pixels that were touched by HairPPLLResolvePass hence preventing depth update unless // it is hair. "Name": "DepthToDepthLinearPass", - "TemplateName": "DepthToLinearTemplate", + "TemplateName": "HairDepthToLinearTemplate", "Enabled": true, "Connections": [ { @@ -343,6 +322,13 @@ "Pass": "HairPPLLResolvePass", "Attachment": "Depth" } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } } ] } diff --git a/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass index d8389a3da8..bb39312ac3 100644 --- a/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass +++ b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass @@ -26,15 +26,10 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "DepthStencil" }, - // Keep DepthLinear as input - used to set the size of the Head PPLL image buffer. - // If DepthLinear is not availbale - connect to another viewport (non MSAA) image. - { - "Name": "DepthLinearInput", - "SlotType": "Input" - }, + // Used to set the size of the Head PPLL image buffer. { "Name": "DepthLinear", - "SlotType": "Output" + "SlotType": "InputOutput" }, // Lights & Shadows resources @@ -63,21 +58,6 @@ "SlotType": "Input" } ], - "Connections": [ - { - "LocalSlot": "DepthLinear", - "AttachmentRef": { - "Pass": "DepthToDepthLinearPass", - "Attachment": "Output" - } - } - ], - "FallbackConnections": [ - { - "Input": "DepthLinearInput", - "Output": "DepthLinear" - } - ], "PassRequests": [ { "Name": "HairGlobalShapeConstraintsComputePass", @@ -277,7 +257,7 @@ "LocalSlot": "DepthLinear", "AttachmentRef": { "Pass": "Parent", - "Attachment": "DepthLinearInput" + "Attachment": "DepthLinear" } }, { @@ -387,7 +367,7 @@ // buffer pixels that were touched by HairPPLLResolvePass hence preventing depth update unless // it is hair. "Name": "DepthToDepthLinearPass", - "TemplateName": "DepthToLinearTemplate", + "TemplateName": "HairDepthToLinearTemplate", "Enabled": true, "Connections": [ { @@ -396,6 +376,13 @@ "Pass": "HairShortCutResolveDepthPass", "Attachment": "Depth" } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } } ] } diff --git a/Gems/AtomTressFX/Assets/Passes/HairResolvePPLL.pass b/Gems/AtomTressFX/Assets/Passes/HairResolvePPLL.pass index 0061fa1666..69e6dd1aab 100644 --- a/Gems/AtomTressFX/Assets/Passes/HairResolvePPLL.pass +++ b/Gems/AtomTressFX/Assets/Passes/HairResolvePPLL.pass @@ -139,6 +139,7 @@ ], "PassData": { "$type": "FullscreenTrianglePassData", + "PipelineViewTag": "MainCamera", "ShaderAsset": { // Looking for it in the Shaders directory relative to the Assets directory "FilePath": "Shaders/HairRenderingResolvePPLL.shader" diff --git a/Gems/AtomTressFX/Assets/seedList.seed b/Gems/AtomTressFX/Assets/seedList.seed index 95389a753a..151e9ac9b3 100644 --- a/Gems/AtomTressFX/Assets/seedList.seed +++ b/Gems/AtomTressFX/Assets/seedList.seed @@ -5,7 +5,7 @@ - + @@ -13,7 +13,7 @@ - + @@ -21,80 +21,16 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp index f35bce391e..baaa361cb2 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp @@ -69,12 +69,6 @@ namespace AZ HairShortCutResolveColorPassName = Name{ "HairShortCutResolveColorPass" }; ++s_instanceCount; - - if (!CreatePerPassResources()) - { // this might not be an error - if the pass system is still empty / minimal - // and these passes are not part of the minimal pipeline, they will not be created. - AZ_Error("Hair Gem", false, "Failed to create the hair shared buffer resource"); - } } HairFeatureProcessor::~HairFeatureProcessor() @@ -106,11 +100,17 @@ namespace AZ void HairFeatureProcessor::Deactivate() { + m_hairPassRequestAsset.Reset(); DisableSceneNotification(); TickBus::Handler::BusDisconnect(); HairGlobalSettingsRequestBus::Handler::BusDisconnect(); } + void HairFeatureProcessor::ApplyRenderPipelineChange(RPI::RenderPipeline* renderPipeline) + { + AddHairParentPass(renderPipeline); + } + void HairFeatureProcessor::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { const float MAX_SIMULATION_TIME_STEP = 0.033f; // Assuming minimal of 30 fps @@ -268,6 +268,12 @@ namespace AZ return; } + // Skip adding draw or dispath items if there it no hair render objects + if (m_hairRenderObjects.size() == 0) + { + return; + } + // [To Do] - no culling scheme applied yet. // Possibly setup the hair culling work group to be re-used for each view. // See SkinnedMeshFeatureProcessor::Render for more details @@ -316,6 +322,53 @@ namespace AZ return pass ? true : false; } + bool HairFeatureProcessor::AddHairParentPass(RPI::RenderPipeline* renderPipeline) + { + if (HasHairParentPass(renderPipeline)) + { + CreatePerPassResources(); + return true; + } + const char* passRequestAssetFilePath = "Passes/AtomTressFX_PassRequest.azasset"; + m_hairPassRequestAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath( + passRequestAssetFilePath, AZ::RPI::AssetUtils::TraceLevel::Warning); + const AZ::RPI::PassRequest* passRequest = nullptr; + if (m_hairPassRequestAsset->IsReady()) + { + passRequest = m_hairPassRequestAsset->GetDataAs(); + } + if (!passRequest) + { + AZ_Error("AtomTressFx", false, "Failed to add hair parent pass. Can't load PassRequest from %s", passRequestAssetFilePath); + return false; + } + + m_usePPLLRenderTechnique = passRequest->m_templateName == AZ::Name("HairParentPassTemplate"); + + // Create the pass + RPI::Ptr hairParentPass = RPI::PassSystemInterface::Get()->CreatePassFromRequest(passRequest); + if (!hairParentPass) + { + AZ_Error("AtomTressFx", false, "Create hair parent pass from pass request failed", + renderPipeline->GetId().GetCStr()); + return false; + } + + // Add the pass to render pipeline + bool success = renderPipeline->AddPassAfter(hairParentPass, Name("OpaquePass")); + // only create pass resources if it was success + if (success) + { + CreatePerPassResources(); + } + else + { + AZ_Error("AtomTressFx", false, "Add the hair parent pass to render pipeline [%s] failed", + renderPipeline->GetId().GetCStr()); + } + return success; + } + void HairFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr renderPipeline) { // Proceed only if this is the main pipeline that contains the parent pass @@ -405,15 +458,11 @@ namespace AZ bool HairFeatureProcessor::CreatePerPassResources() { - if (m_sharedResourcesCreated) - { - return true; - } - SrgBufferDescriptor descriptor; AZStd::string instanceNumber = AZStd::to_string(s_instanceCount); // Shared buffer - this is a persistent buffer that needs to be created manually. + if (!m_sharedDynamicBuffer) { AZStd::vector hairDynamicDescriptors; DynamicHairData::PrepareSrgDescriptors(hairDynamicDescriptors, 1, 1); @@ -426,7 +475,7 @@ namespace AZ } // PPLL nodes buffer - created only if the PPLL technique is used - if (m_usePPLLRenderTechnique) + if (m_usePPLLRenderTechnique && !m_linkedListNodesBuffer) { descriptor = SrgBufferDescriptor( RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown, @@ -441,7 +490,6 @@ namespace AZ } } - m_sharedResourcesCreated = true; return true; } diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h index 8c19b706a9..1f75ae7499 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h @@ -113,6 +113,7 @@ namespace AZ // FeatureProcessor overrides ... void Activate() override; void Deactivate() override; + void ApplyRenderPipelineChange(RPI::RenderPipeline* renderPipeline) override; void Simulate(const FeatureProcessor::SimulatePacket& packet) override; void Render(const FeatureProcessor::RenderPacket& packet) override; @@ -167,6 +168,8 @@ namespace AZ bool HasHairParentPass(RPI::RenderPipeline* renderPipeline); + bool AddHairParentPass(RPI::RenderPipeline* renderPipeline); + //! The following will serve to register the FP in the Thumbnail system AZStd::vector m_hairFeatureProcessorRegistryName; @@ -193,6 +196,9 @@ namespace AZ Data::Instance m_hairShortCutGeometryDepthAlphaPass = nullptr; Data::Instance m_hairShortCutGeometryShadingPass = nullptr; + // Cache the pass request data for creating a hair parent pass + AZ::Data::Asset m_hairPassRequestAsset; + //-------------------------------------------------------------- // Per Pass Resources //-------------------------------------------------------------- @@ -211,7 +217,6 @@ namespace AZ float m_currentDeltaTime = 0.02f; //! flag to disable/enable feature processor adding dispatch calls to compute passes. bool m_addDispatchEnabled = true; - bool m_sharedResourcesCreated = false; //! reload / pipeline changes force build dispatches and render items bool m_forceRebuildRenderData = false; bool m_forceClearRenderData = false;