Fix for ATOM-15488 : Rendering out an animation with shadows crashes the editor (#794)

Previously, the SkinnedMeshFeatureProcessor assumed there would only be one skinning pass. However, that's not always the case. When rendering with track view, the feature processor was getting a pass that only updated once every three frames, which could lead to a condition where a skinned mesh was released, but the pass never submitted and cleared the previously added dispatch items, and one or two frames later it would go to submit after the skinned mesh and all of its resources had already been released.

-Modified the skinning and morph target compute passes to pull dispatch items from the feature processor instead of the feature processor pushing them to the passes.
-If more than one skinning (or morph target) pass is active in the frame, whichever one is first will submit all the dispatch items, and clear the feature processor's dispatch items before the next one tries to submit anything
-Moved the logic for caching shader options from the SkinnedMeshComputePass to the SkinnedMeshFeatureProcessor, since there may be more than one pass but only one feature processor per scene
This commit is contained in:
Tommy Walton
2021-05-18 13:52:01 -07:00
committed by GitHub
parent 3c40aad148
commit 3182dc37c3
11 changed files with 149 additions and 146 deletions
@@ -12,6 +12,7 @@
#include <MorphTargets/MorphTargetComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@@ -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<AZStd::mutex> 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<AZStd::mutex> 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
@@ -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<MorphTargetComputePass> 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<RPI::Shader> 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<const RHI::DispatchItem*> m_dispatches;
SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr;
};
}
}
@@ -11,7 +11,7 @@
*/
#include <MorphTargets/MorphTargetDispatchItem.h>
#include <MorphTargets/MorphTargetComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@@ -30,7 +30,7 @@ namespace AZ
MorphTargetDispatchItem::MorphTargetDispatchItem(
const AZStd::intrusive_ptr<MorphTargetInputBuffers> inputBuffers,
const MorphTargetMetaData& morphTargetMetaData,
RPI::Ptr<MorphTargetComputePass> 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());
}
@@ -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<MorphTargetInputBuffers> inputBuffers,
const MorphTargetMetaData& morphTargetMetaData,
RPI::Ptr<MorphTargetComputePass> morphTargetComputePass,
SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor,
MorphTargetInstanceMetaData morphInstanceMetaData,
float accumulatedDeltaRange
);
@@ -12,6 +12,7 @@
#include <SkinnedMesh/SkinnedMeshComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@@ -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> 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<AZStd::mutex> 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<AZStd::mutex> 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)
@@ -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<SkinnedMeshComputePass> 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<RPI::Shader> 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<const RHI::DispatchItem*> m_dispatches;
CachedSkinnedMeshShaderOptions m_cachedShaderOptions;
SkinnedMeshFeatureProcessor* m_skinnedMeshFeatureProcessor = nullptr;
};
}
}
@@ -12,7 +12,7 @@
#include <SkinnedMesh/SkinnedMeshDispatchItem.h>
#include <SkinnedMesh/SkinnedMeshOutputStreamManager.h>
#include <SkinnedMesh/SkinnedMeshComputePass.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/Shader.h>
@@ -34,7 +34,7 @@ namespace AZ
size_t lodIndex,
Data::Instance<RPI::Buffer> boneTransforms,
const SkinnedMeshShaderOptions& shaderOptions,
RPI::Ptr<SkinnedMeshComputePass> 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()
@@ -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<RPI::Buffer> skinningMatrices,
const SkinnedMeshShaderOptions& shaderOptions,
RPI::Ptr<SkinnedMeshComputePass> skinnedMeshComputePass,
SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor,
MorphTargetInstanceMetaData morphTargetInstanceMetaData,
float morphTargetDeltaIntegerEncoding
);
@@ -24,8 +24,10 @@
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RHI/CommandList.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Jobs/JobCompletion.h>
@@ -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<RPI::ParentPass> 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<RPI::Pass> skinningPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "SkinningPass" });
if (skinningPass)
{
auto& skinningPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "SkinningPassTemplate" });
SkinnedMeshComputePass* skinnedMeshComputePass = azdynamic_cast<SkinnedMeshComputePass*>(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<SkinnedMeshComputePass*>(skinningPasses[0]);
const Data::Instance<RPI::Shader> 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<RPI::Pass> morphTargetPass = pipelineRootPass->FindPassByNameRecursive(AZ::Name{ "MorphTargetPass" });
if (morphTargetPass)
{
auto& morphTargetPasses = passSystem->GetPassesForTemplateName(AZ::Name{ "MorphTargetPassTemplate" });
MorphTargetComputePass* morphTargetComputePass = azdynamic_cast<MorphTargetComputePass*>(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<MorphTargetComputePass*>(morphTargetPasses[0]);
const Data::Instance<RPI::Shader> 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<RPI::Shader> 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<SkinnedMeshComputePass> SkinnedMeshFeatureProcessor::GetSkinningPass() const
Data::Instance<RPI::Shader> SkinnedMeshFeatureProcessor::GetSkinningShader() const
{
return m_skinningPass;
return m_skinningShader;
}
RPI::Ptr<MorphTargetComputePass> SkinnedMeshFeatureProcessor::GetMorphTargetPass() const
Data::Instance<RPI::Shader> SkinnedMeshFeatureProcessor::GetMorphTargetShader() const
{
return m_morphTargetPass;
return m_morphTargetShader;
}
} // namespace Render
} // namespace AZ
@@ -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<SkinnedMeshComputePass> GetSkinningPass() const;
RPI::Ptr<MorphTargetComputePass> GetMorphTargetPass() const;
Data::Instance<RPI::Shader> GetSkinningShader() const;
RPI::ShaderOptionGroup CreateSkinningShaderOptionGroup(const SkinnedMeshShaderOptions shaderOptions, SkinnedMeshShaderOptionNotificationBus::Handler& shaderReinitializedHandler);
void OnSkinningShaderReinitialized(const Data::Instance<RPI::Shader> skinningShader);
void SubmitSkinningDispatchItems(RHI::CommandList* commandList);
Data::Instance<RPI::Shader> GetMorphTargetShader() const;
void SubmitMorphTargetDispatchItems(RHI::CommandList* commandList);
private:
AZ_DISABLE_COPY_MOVE(SkinnedMeshFeatureProcessor);
void InitSkinningAndMorphPass();
void InitSkinningAndMorphPass(const RPI::Ptr<RPI::ParentPass> pipelineRootPass);
SkinnedMeshRenderProxyInterfaceHandle AcquireRenderProxyInterface(const SkinnedMeshRenderProxyDesc& desc) override;
bool ReleaseRenderProxyInterface(SkinnedMeshRenderProxyInterfaceHandle& handle) override;
static const char* s_featureProcessorName;
RPI::Ptr<SkinnedMeshComputePass> m_skinningPass;
RPI::Ptr<MorphTargetComputePass> m_morphTargetPass;
Data::Instance<RPI::Shader> m_skinningShader;
CachedSkinnedMeshShaderOptions m_cachedSkinningShaderOptions;
Data::Instance<RPI::Shader> m_morphTargetShader;
AZStd::concurrency_checker m_renderProxiesChecker;
StableDynamicArray<SkinnedMeshRenderProxy> m_renderProxies;
AZStd::unique_ptr<SkinnedMeshStatsCollector> m_statsCollector;
MeshFeatureProcessor* m_meshFeatureProcessor = nullptr;
AZStd::unordered_set<const RHI::DispatchItem*> m_skinningDispatches;
AZStd::unordered_set<const RHI::DispatchItem*> m_morphTargetDispatches;
AZStd::mutex m_dispatchItemMutex;
};
} // namespace Render
@@ -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<RPI::Shader> skinningShader = m_featureProcessor->GetSkinningPass()->GetShader();
Data::Instance<RPI::Shader> 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<RPI::Shader> morphTargetShader = m_featureProcessor->GetMorphTargetPass()->GetShader();
Data::Instance<RPI::Shader> morphTargetShader = m_featureProcessor->GetMorphTargetShader();
const AZStd::vector<AZStd::intrusive_ptr<MorphTargetInputBuffers>>& 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 });