diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp index 35793b82a3..1bb537ecef 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.cpp @@ -12,6 +12,7 @@ #include +#include #include #include @@ -38,6 +39,11 @@ namespace AZ return m_shader; } + void MorphTargetComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor) + { + m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor; + } + void MorphTargetComputePass::BuildAttachmentsInternal() { // The same buffer that skinning writes to is used to manage the computed vertex deltas that are passed from the @@ -45,30 +51,16 @@ namespace AZ AttachBufferToSlot(Name{ "MorphTargetDeltaOutput" }, SkinnedMeshOutputStreamManagerInterface::Get()->GetBuffer()); } - void MorphTargetComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem) - { - AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem"); - - AZStd::lock_guard lock(m_mutex); - //using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue - //(i.e. if the same morph target exists in multiple views, it can call AddDispatchItem multiple times with the same item) - m_dispatches.insert(dispatchItem); - } - void MorphTargetComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) { - RHI::CommandList* commandList = context.GetCommandList(); - - SetSrgsForDispatch(commandList); - - AZStd::lock_guard lock(m_mutex); - for (const RHI::DispatchItem* dispatchItem : m_dispatches) + if (m_skinnedMeshFeatureProcessor) { - commandList->Submit(*dispatchItem); - } + RHI::CommandList* commandList = context.GetCommandList(); - // Clear the dispatch items. They will need to be re-populated next frame - m_dispatches.clear(); + SetSrgsForDispatch(commandList); + + m_skinnedMeshFeatureProcessor->SubmitMorphTargetDispatchItems(commandList); + } } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h index 3967d9190e..61fa485fbc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetComputePass.h @@ -18,6 +18,8 @@ namespace AZ { namespace Render { + class SkinnedMeshFeatureProcessor; + //! The morph target compute pass submits dispatch items for morph targets. The dispatch items are cleared every frame, so it needs to be re-populated. class MorphTargetComputePass : public RPI::ComputePass @@ -31,16 +33,14 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); - //! Thread-safe function for adding a dispatch item to the current frame. - void AddDispatchItem(const RHI::DispatchItem* dispatchItem); Data::Instance GetShader() const; + void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor); private: void BuildAttachmentsInternal() override; void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; - AZStd::mutex m_mutex; - AZStd::unordered_set m_dispatches; + SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp index d7bbc315ed..7b3aedd64e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include @@ -30,7 +30,7 @@ namespace AZ MorphTargetDispatchItem::MorphTargetDispatchItem( const AZStd::intrusive_ptr inputBuffers, const MorphTargetMetaData& morphTargetMetaData, - RPI::Ptr morphTargetComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphInstanceMetaData, float morphDeltaIntegerEncoding) : m_inputBuffers(inputBuffers) @@ -38,7 +38,7 @@ namespace AZ , m_morphInstanceMetaData(morphInstanceMetaData) , m_accumulatedDeltaIntegerEncoding(morphDeltaIntegerEncoding) { - m_morphTargetShader = morphTargetComputePass->GetShader(); + m_morphTargetShader = skinnedMeshFeatureProcessor->GetMorphTargetShader(); RPI::ShaderReloadNotificationBus::Handler::BusConnect(m_morphTargetShader->GetAssetId()); } diff --git a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h index 46680eb465..ad1fd969a5 100644 --- a/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h +++ b/Gems/Atom/Feature/Common/Code/Source/MorphTargets/MorphTargetDispatchItem.h @@ -37,7 +37,7 @@ namespace AZ namespace Render { - class MorphTargetComputePass; + class SkinnedMeshFeatureProcessor; //! Holds and manages an RHI DispatchItem for a specific morph target, and the resources that are needed to build and maintain it. class MorphTargetDispatchItem @@ -51,7 +51,7 @@ namespace AZ explicit MorphTargetDispatchItem( const AZStd::intrusive_ptr inputBuffers, const MorphTargetMetaData& morphTargetMetaData, - RPI::Ptr morphTargetComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphInstanceMetaData, float accumulatedDeltaRange ); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp index d7b2c605b4..a3feddb0b6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.cpp @@ -12,6 +12,7 @@ #include +#include #include #include @@ -22,11 +23,9 @@ namespace AZ { namespace Render { - SkinnedMeshComputePass::SkinnedMeshComputePass(const RPI::PassDescriptor& descriptor) : RPI::ComputePass(descriptor) { - m_cachedShaderOptions.SetShader(m_shader); } RPI::Ptr SkinnedMeshComputePass::Create(const RPI::PassDescriptor& descriptor) @@ -40,42 +39,30 @@ namespace AZ return m_shader; } - RPI::ShaderOptionGroup SkinnedMeshComputePass::CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler) + void SkinnedMeshComputePass::SetFeatureProcessor(SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor) { - m_cachedShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler); - return m_cachedShaderOptions.CreateShaderOptionGroup(shaderOptions); - } - - void SkinnedMeshComputePass::AddDispatchItem(const RHI::DispatchItem* dispatchItem) - { - AZ_Assert(dispatchItem != nullptr, "invalid dispatchItem"); - - AZStd::lock_guard lock(m_mutex); - //using an unordered_set here to prevent redundantly adding the same dispatchItem to the submission queue - //(i.e. if the same skinnedMesh exists in multiple views, it can call AddDispatchItem multiple times with the same item) - m_dispatches.insert(dispatchItem); + m_skinnedMeshFeatureProcessor = skinnedMeshFeatureProcessor; } void SkinnedMeshComputePass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) { - RHI::CommandList* commandList = context.GetCommandList(); - - SetSrgsForDispatch(commandList); - - AZStd::lock_guard lock(m_mutex); - for (const RHI::DispatchItem* dispatchItem : m_dispatches) + if (m_skinnedMeshFeatureProcessor) { - commandList->Submit(*dispatchItem); - } + RHI::CommandList* commandList = context.GetCommandList(); - // Clear the dispatch items. They will need to be re-populated next frame - m_dispatches.clear(); + SetSrgsForDispatch(commandList); + + m_skinnedMeshFeatureProcessor->SubmitSkinningDispatchItems(commandList); + } } void SkinnedMeshComputePass::OnShaderReinitialized(const RPI::Shader& shader) { ComputePass::OnShaderReinitialized(shader); - m_cachedShaderOptions.SetShader(m_shader); + if (m_skinnedMeshFeatureProcessor) + { + m_skinnedMeshFeatureProcessor->OnSkinningShaderReinitialized(m_shader); + } } void SkinnedMeshComputePass::OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId&, RPI::ShaderVariantStableId) diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h index d2e0fb77dc..5f7ff08e47 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshComputePass.h @@ -20,6 +20,8 @@ namespace AZ { namespace Render { + class SkinnedMeshFeatureProcessor; + //! The skinned mesh compute pass submits dispatch items for skinning. The dispatch items are cleared every frame, so it needs to be re-populated. class SkinnedMeshComputePass : public RPI::ComputePass @@ -33,10 +35,9 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); - //! Thread-safe function for adding a dispatch item to the current frame. - void AddDispatchItem(const RHI::DispatchItem* dispatchItem); Data::Instance GetShader() const; - RPI::ShaderOptionGroup CreateShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler); + + void SetFeatureProcessor(SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor); private: void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; @@ -45,9 +46,7 @@ namespace AZ void OnShaderReinitialized(const RPI::Shader& shader) override; void OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId shaderVariantStableId) override; - AZStd::mutex m_mutex; - AZStd::unordered_set m_dispatches; - CachedSkinnedMeshShaderOptions m_cachedShaderOptions; + SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp index c9054fc6c8..647a87a788 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -34,7 +34,7 @@ namespace AZ size_t lodIndex, Data::Instance boneTransforms, const SkinnedMeshShaderOptions& shaderOptions, - RPI::Ptr skinnedMeshComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphTargetInstanceMetaData, float morphTargetDeltaIntegerEncoding) : m_inputBuffers(inputBuffers) @@ -45,7 +45,7 @@ namespace AZ , m_morphTargetInstanceMetaData(morphTargetInstanceMetaData) , m_morphTargetDeltaIntegerEncoding(morphTargetDeltaIntegerEncoding) { - m_skinningShader = skinnedMeshComputePass->GetShader(); + m_skinningShader = skinnedMeshFeatureProcessor->GetSkinningShader(); // Shader options are generally set per-skinned mesh instance, but morph targets may only exist on some lods. Override the option for applying morph targets here if (m_morphTargetInstanceMetaData.m_accumulatedPositionDeltaOffsetInBytes != MorphTargetConstants::s_invalidDeltaOffset) @@ -58,7 +58,7 @@ namespace AZ } // CreateShaderOptionGroup will also connect to the SkinnedMeshShaderOptionNotificationBus - m_shaderOptionGroup = skinnedMeshComputePass->CreateShaderOptionGroup(m_shaderOptions, *this); + m_shaderOptionGroup = skinnedMeshFeatureProcessor->CreateSkinningShaderOptionGroup(m_shaderOptions, *this); } SkinnedMeshDispatchItem::~SkinnedMeshDispatchItem() diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h index c80b002106..33bec89afd 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.h @@ -38,7 +38,7 @@ namespace AZ namespace Render { - class SkinnedMeshComputePass; + class SkinnedMeshFeatureProcessor; //! Holds and manages an RHI DispatchItem for a specific skinned mesh, and the resources that are needed to build and maintain it. class SkinnedMeshDispatchItem @@ -55,7 +55,7 @@ namespace AZ size_t lodIndex, Data::Instance skinningMatrices, const SkinnedMeshShaderOptions& shaderOptions, - RPI::Ptr skinnedMeshComputePass, + SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor, MorphTargetInstanceMetaData morphTargetInstanceMetaData, float morphTargetDeltaIntegerEncoding ); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp index 193a36e588..388f4112a0 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.cpp @@ -24,8 +24,10 @@ #include #include #include +#include #include +#include #include #include @@ -84,11 +86,6 @@ namespace AZ AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); AZ_ATOM_PROFILE_FUNCTION("SkinnedMesh", "SkinnedMeshFeatureProcessor: Render"); - if (!m_skinningPass) - { - return; - } - #if 0 //[GFX_TODO][ATOM-13564] Temporarily disable skinning culling until we figure out how to hook up visibility & lod selection with skinning: //Setup the culling workgroup (it will be re-used for each view) { @@ -132,7 +129,7 @@ namespace AZ //Dispatch the workgroup to each view for (const RPI::ViewPtr& viewPtr : packet.m_views) { - Job *processWorkgroupJob = AZ::CreateJobFunction( + Job* processWorkgroupJob = AZ::CreateJobFunction( [this, cullingSystem, viewPtr](AZ::Job& thisJob) { AZ_PROFILE_SCOPE_DYNAMIC(Debug::ProfileCategory::AzRender, "skinningMeshFP processWorkgroupJob - View: %s", viewPtr->GetName().GetCStr()); @@ -167,7 +164,16 @@ namespace AZ float maxScreenPercentage(lod.m_range.m_max); if (approxScreenPercentage >= minScreenPercentage && approxScreenPercentage <= maxScreenPercentage) { - m_skinningPass->AddDispatchItem(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + AZStd::lock_guard lock(m_dispatchItemMutex); + m_skinningDispatches.insert(&renderProxy->m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy->m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++) + { + const MorphTargetDispatchItem* dispatchItem = renderProxy->m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get(); + if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon) + { + m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem()); + } + } } } } @@ -232,13 +238,14 @@ namespace AZ //Note that this supports overlapping lod ranges (to support cross-fading lods, for example) if (approxScreenPercentage >= lod.m_screenCoverageMin && approxScreenPercentage <= lod.m_screenCoverageMax) { - m_skinningPass->AddDispatchItem(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); + AZStd::lock_guard lock(m_dispatchItemMutex); + m_skinningDispatches.insert(&renderProxy.m_dispatchItemsByLod[lodIndex]->GetRHIDispatchItem()); for (size_t morphTargetIndex = 0; morphTargetIndex < renderProxy.m_morphTargetDispatchItemsByLod[lodIndex].size(); morphTargetIndex++) { const MorphTargetDispatchItem* dispatchItem = renderProxy.m_morphTargetDispatchItemsByLod[lodIndex][morphTargetIndex].get(); if (dispatchItem && dispatchItem->GetWeight() > AZ::Constants::FloatEpsilon) { - m_morphTargetPass->AddDispatchItem(&dispatchItem->GetRHIDispatchItem()); + m_morphTargetDispatches.insert(&dispatchItem->GetRHIDispatchItem()); } } } @@ -248,19 +255,14 @@ namespace AZ #endif } - void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] RPI::RenderPipelinePtr pipeline) + void SkinnedMeshFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { - InitSkinningAndMorphPass(); + InitSkinningAndMorphPass(pipeline->GetRootPass()); } - void SkinnedMeshFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] RPI::RenderPipeline* pipeline) + void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) { - InitSkinningAndMorphPass(); - } - - void SkinnedMeshFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] RPI::RenderPipeline* renderPipeline) - { - InitSkinningAndMorphPass(); + InitSkinningAndMorphPass(renderPipeline->GetRootPass()); } void SkinnedMeshFeatureProcessor::OnBeginPrepareRender() @@ -268,9 +270,15 @@ namespace AZ m_renderProxiesChecker.soft_lock(); } - void SkinnedMeshFeatureProcessor::OnEndPrepareRender() + void SkinnedMeshFeatureProcessor::OnRenderEnd() { m_renderProxiesChecker.soft_unlock(); + + // Clear any dispatch items that were added but never submitted + // in case there were no passes that submitted this frame + // because they execute at a lower frequency + m_skinningDispatches.clear(); + m_morphTargetDispatches.clear(); } SkinnedMeshRenderProxyHandle SkinnedMeshFeatureProcessor::AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc) @@ -295,61 +303,73 @@ namespace AZ return false; } - void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass() + void SkinnedMeshFeatureProcessor::InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass) { - m_skinningPass = nullptr; //reset it to null, just in case it fails to load the assets properly - m_morphTargetPass = nullptr; - - RPI::PassSystemInterface* passSystem = RPI::PassSystemInterface::Get(); - if (passSystem->HasPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" })) + RPI::Ptr skinningPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "SkinningPass" }); + if (skinningPass) { - auto& skinningPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" }); + SkinnedMeshComputePass* skinnedMeshComputePass = azdynamic_cast(skinningPass.get()); + skinnedMeshComputePass->SetFeatureProcessor(this); - // For now, assume one skinning pass - if (!skinningPasses.empty() && skinningPasses[0]) + // There may be multiple skinning passes in the scene due to multiple pipelines, but there is only one skinning shader + m_skinningShader = skinnedMeshComputePass->GetShader(); + + if (!m_skinningShader) { - m_skinningPass = static_cast(skinningPasses[0]); - const Data::Instance shader = m_skinningPass->GetShader(); - - if (!shader) - { - AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing."); - } + AZ_Error(s_featureProcessorName, false, "Failed to get skinning pass shader. It may need to finish processing."); } else { - AZ_Error(s_featureProcessorName, false, "\"SkinningPassTemplate\" does not have any valid passes. Check your game project's .pass assets."); + m_cachedSkinningShaderOptions.SetShader(m_skinningShader); } } - else - { - AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"SkinningPassTemplate\". Check your game project's .pass assets."); - } - if (passSystem->HasPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" })) + RPI::Ptr morphTargetPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "MorphTargetPass" }); + if (morphTargetPass) { - auto& morphTargetPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" }); + MorphTargetComputePass* morphTargetComputePass = azdynamic_cast(morphTargetPass.get()); + morphTargetComputePass->SetFeatureProcessor(this); - // For now, assume one skinning pass - if (!morphTargetPasses.empty() && morphTargetPasses[0]) + // There may be multiple morph target passes in the scene due to multiple pipelines, but there is only one morph target shader + m_morphTargetShader = morphTargetComputePass->GetShader(); + + if (!m_morphTargetShader) { - m_morphTargetPass = static_cast(morphTargetPasses[0]); - const Data::Instance shader = m_morphTargetPass->GetShader(); + AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing."); + } + } + } - if (!shader) - { - AZ_Error(s_featureProcessorName, false, "Failed to get morph target pass shader. It may need to finish processing."); - } - } - else - { - AZ_Error(s_featureProcessorName, false, "\"MorphTargetPassTemplate\" does not have any valid passes. Check your game project's .pass assets."); - } - } - else + RPI::ShaderOptionGroup SkinnedMeshFeatureProcessor::CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler) + { + m_cachedSkinningShaderOptions.ConnectToShaderReinitializedEvent(shaderReinitializedHandler); + return m_cachedSkinningShaderOptions.CreateShaderOptionGroup(shaderOptions); + } + + void SkinnedMeshFeatureProcessor::OnSkinningShaderReinitialized(const Data::Instance skinningShader) + { + m_skinningShader = skinningShader; + m_cachedSkinningShaderOptions.SetShader(m_skinningShader); + } + + void SkinnedMeshFeatureProcessor::SubmitSkinningDispatchItems(RHI::CommandList* commandList) + { + AZStd::lock_guard lock(m_dispatchItemMutex); + for (const RHI::DispatchItem* dispatchItem : m_skinningDispatches) { - AZ_Error(s_featureProcessorName, false, "Failed to find passes for \"MorphTargetPassTemplate\". Check your game project's .pass assets."); + commandList->Submit(*dispatchItem); } + m_skinningDispatches.clear(); + } + + void SkinnedMeshFeatureProcessor::SubmitMorphTargetDispatchItems(RHI::CommandList* commandList) + { + AZStd::lock_guard lock(m_dispatchItemMutex); + for (const RHI::DispatchItem* dispatchItem : m_morphTargetDispatches) + { + commandList->Submit(*dispatchItem); + } + m_morphTargetDispatches.clear(); } SkinnedMeshRenderProxyInterfaceHandle SkinnedMeshFeatureProcessor::AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) @@ -363,14 +383,14 @@ namespace AZ return ReleaseRenderProxy(handle); } - RPI::Ptr SkinnedMeshFeatureProcessor::GetSkinningPass() const + Data::Instance SkinnedMeshFeatureProcessor::GetSkinningShader() const { - return m_skinningPass; + return m_skinningShader; } - RPI::Ptr SkinnedMeshFeatureProcessor::GetMorphTargetPass() const + Data::Instance SkinnedMeshFeatureProcessor::GetMorphTargetShader() const { - return m_morphTargetPass; + return m_morphTargetShader; } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h index 86dedfa161..bb3dd242a1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshFeatureProcessor.h @@ -51,35 +51,46 @@ namespace AZ void Deactivate() override; void Simulate(const FeatureProcessor::SimulatePacket& packet) override; void Render(const FeatureProcessor::RenderPacket& packet) override; + void OnRenderEnd() override; // RPI::SceneNotificationBus overrides ... void OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) override; - void OnRenderPipelineRemoved(RPI::RenderPipeline* pipeline) override; void OnRenderPipelinePassesChanged(RPI::RenderPipeline* renderPipeline) override; void OnBeginPrepareRender() override; - void OnEndPrepareRender() override; SkinnedMeshRenderProxyHandle AcquireRenderProxy(const SkinnedMeshRenderProxyDesc& desc); bool ReleaseRenderProxy(SkinnedMeshRenderProxyHandle& handle); - RPI::Ptr GetSkinningPass() const; - RPI::Ptr GetMorphTargetPass() const; + Data::Instance GetSkinningShader() const; + RPI::ShaderOptionGroup CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler); + void OnSkinningShaderReinitialized(const Data::Instance skinningShader); + void SubmitSkinningDispatchItems(RHI::CommandList* commandList); + + Data::Instance GetMorphTargetShader() const; + void SubmitMorphTargetDispatchItems(RHI::CommandList* commandList); private: AZ_DISABLE_COPY_MOVE(SkinnedMeshFeatureProcessor); - void InitSkinningAndMorphPass(); + void InitSkinningAndMorphPass(const RPI::Ptr pipelineRootPass); SkinnedMeshRenderProxyInterfaceHandle AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) override; bool ReleaseRenderProxyInterface(SkinnedMeshRenderProxyInterfaceHandle& handle) override; static const char* s_featureProcessorName; - RPI::Ptr m_skinningPass; - RPI::Ptr m_morphTargetPass; + + Data::Instance m_skinningShader; + CachedSkinnedMeshShaderOptions m_cachedSkinningShaderOptions; + + Data::Instance m_morphTargetShader; + AZStd::concurrency_checker m_renderProxiesChecker; StableDynamicArray m_renderProxies; AZStd::unique_ptr m_statsCollector; MeshFeatureProcessor* m_meshFeatureProcessor = nullptr; + AZStd::unordered_set m_skinningDispatches; + AZStd::unordered_set m_morphTargetDispatches; + AZStd::mutex m_dispatchItemMutex; }; } // namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp index 90c54dfc10..090b720406 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshRenderProxy.cpp @@ -60,13 +60,7 @@ namespace AZ bool SkinnedMeshRenderProxy::BuildDispatchItem([[maybe_unused]] const RPI::Scene& scene, size_t modelLodIndex, [[maybe_unused]] const SkinnedMeshShaderOptions& shaderOptions) { - if (!m_featureProcessor->GetSkinningPass()) - { - AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get Skinning Pass. Make sure the project has a skinning pass."); - return false; - } - - Data::Instance skinningShader = m_featureProcessor->GetSkinningPass()->GetShader(); + Data::Instance skinningShader = m_featureProcessor->GetSkinningShader(); if (!skinningShader) { AZ_Error("Skinned Mesh Feature Processor", false, "Failed to get skinning shader from skinning pass"); @@ -89,7 +83,7 @@ namespace AZ m_instance->m_outputStreamOffsetsInBytes[modelLodIndex], modelLodIndex, m_boneTransforms, m_shaderOptions, - m_featureProcessor->GetSkinningPass(), + m_featureProcessor, m_instance->m_morphTargetInstanceMetaData[modelLodIndex], morphDeltaIntegerEncoding }); @@ -100,7 +94,7 @@ namespace AZ } // Get the data needed to create a morph target dispatch item - Data::Instance morphTargetShader = m_featureProcessor->GetMorphTargetPass()->GetShader(); + Data::Instance morphTargetShader = m_featureProcessor->GetMorphTargetShader(); const AZStd::vector>& morphTargetInputBuffersVector = m_inputBuffers->GetMorphTargetInputBuffers(modelLodIndex); AZ_Assert(morphTargetMetaDatas.size() == morphTargetInputBuffersVector.size(), "Skinned Mesh Feature Processor - Mismatch in morph target metadata count and morph target input buffer count"); @@ -118,7 +112,7 @@ namespace AZ aznew MorphTargetDispatchItem{ morphTargetInputBuffersVector[morphTargetIndex], morphTargetMetaDatas[morphTargetIndex], - m_featureProcessor->GetMorphTargetPass(), + m_featureProcessor, m_instance->m_morphTargetInstanceMetaData[modelLodIndex], morphDeltaIntegerEncoding });