Files
o3de/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp
T
Tommy Walton 3182dc37c3 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
2021-05-18 13:52:01 -07:00

291 lines
16 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SkinnedMesh/SkinnedMeshDispatchItem.h>
#include <SkinnedMesh/SkinnedMeshOutputStreamManager.h>
#include <SkinnedMesh/SkinnedMeshFeatureProcessor.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Model/ModelLod.h>
#include <Atom/RPI.Public/Buffer/Buffer.h>
#include <Atom/RHI/Factory.h>
#include <Atom/RHI/BufferView.h>
#include <limits>
namespace AZ
{
namespace Render
{
SkinnedMeshDispatchItem::SkinnedMeshDispatchItem(
AZStd::intrusive_ptr<SkinnedMeshInputBuffers> inputBuffers,
const AZStd::vector<uint32_t>& outputBufferOffsetsInBytes,
size_t lodIndex,
Data::Instance<RPI::Buffer> boneTransforms,
const SkinnedMeshShaderOptions& shaderOptions,
SkinnedMeshFeatureProcessor* skinnedMeshFeatureProcessor,
MorphTargetInstanceMetaData morphTargetInstanceMetaData,
float morphTargetDeltaIntegerEncoding)
: m_inputBuffers(inputBuffers)
, m_outputBufferOffsetsInBytes(outputBufferOffsetsInBytes)
, m_lodIndex(lodIndex)
, m_boneTransforms(AZStd::move(boneTransforms))
, m_shaderOptions(shaderOptions)
, m_morphTargetInstanceMetaData(morphTargetInstanceMetaData)
, m_morphTargetDeltaIntegerEncoding(morphTargetDeltaIntegerEncoding)
{
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)
{
m_shaderOptions.m_applyMorphTargets = true;
}
if (inputBuffers->GetLod(lodIndex).HasDynamicColors())
{
m_shaderOptions.m_applyColorMorphTargets = true;
}
// CreateShaderOptionGroup will also connect to the SkinnedMeshShaderOptionNotificationBus
m_shaderOptionGroup = skinnedMeshFeatureProcessor->CreateSkinningShaderOptionGroup(m_shaderOptions, *this);
}
SkinnedMeshDispatchItem::~SkinnedMeshDispatchItem()
{
SkinnedMeshShaderOptionNotificationBus::Handler::BusDisconnect();
}
bool SkinnedMeshDispatchItem::Init()
{
if (!m_skinningShader)
{
AZ_Error("SkinnedMeshDispatchItem", false, "Cannot initialize a SkinnedMeshDispatchItem with a null shader");
return false;
}
// Get the shader variant and instance SRG
m_shaderOptionGroup.SetUnspecifiedToDefaultValues();
const RPI::ShaderVariant& shaderVariant = m_skinningShader->GetVariant(m_shaderOptionGroup.GetShaderVariantId());
RHI::PipelineStateDescriptorForDispatch pipelineStateDescriptor;
shaderVariant.ConfigurePipelineState(pipelineStateDescriptor);
auto perInstanceSrgAsset = m_skinningShader->FindShaderResourceGroupAsset(AZ::Name{ "InstanceSrg" });
if (!perInstanceSrgAsset.GetId().IsValid())
{
AZ_Error("SkinnedMeshDispatchItem", false, "Failed to get shader resource group asset");
return false;
}
else if (!perInstanceSrgAsset.IsReady())
{
AZ_Error("SkinnedMeshDispatchItem", false, "Shader resource group asset is not loaded");
return false;
}
m_instanceSrg = RPI::ShaderResourceGroup::Create(perInstanceSrgAsset);
if (!m_instanceSrg)
{
AZ_Error("SkinnedMeshDispatchItem", false, "Failed to create shader resource group for skinned mesh");
return false;
}
// If the shader variation is not fully baked, set the fallback key to use a runtime branch for the shader options
if (!shaderVariant.IsFullyBaked() && m_instanceSrg->HasShaderVariantKeyFallbackEntry())
{
m_instanceSrg->SetShaderVariantKeyFallbackValue(m_shaderOptionGroup.GetShaderVariantKeyFallbackValue());
}
m_inputBuffers->SetBufferViewsOnShaderResourceGroup(m_lodIndex, m_instanceSrg);
// Set the SRG indices
RHI::ShaderInputBufferIndex actorInstanceBoneTransformsIndex;
if (m_shaderOptions.m_skinningMethod == SkinningMethod::LinearSkinning)
{
actorInstanceBoneTransformsIndex = m_instanceSrg->FindShaderInputBufferIndex(Name{ "m_boneTransformsLinear" });
if (!actorInstanceBoneTransformsIndex.IsValid())
{
AZ_Error("SkinnedMeshDispatchItem", false, "Failed to find shader input index for m_boneTransformsLinear in the skinning compute shader per-instance SRG.");
return false;
}
}
else if(m_shaderOptions.m_skinningMethod == SkinningMethod::DualQuaternion)
{
actorInstanceBoneTransformsIndex = m_instanceSrg->FindShaderInputBufferIndex(Name{ "m_boneTransformsDualQuaternion" });
if (!actorInstanceBoneTransformsIndex.IsValid())
{
AZ_Error("SkinnedMeshDispatchItem", false, "Failed to find shader input index for m_boneTransformsDualQuaternion in the skinning compute shader per-instance SRG.");
return false;
}
}
else
{
AZ_Assert(false, "Invalid skinning method for SkinnedMeshDispatchItem.");
}
AZ_Assert(aznumeric_cast<uint8_t>(m_outputBufferOffsetsInBytes.size()) == static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams) && m_shaderOptions.m_applyColorMorphTargets
|| aznumeric_cast<uint8_t>(m_outputBufferOffsetsInBytes.size()) == static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams) - 1 && !m_shaderOptions.m_applyColorMorphTargets,
"Not enough offsets were given to the SkinnedMeshDispatchItem");
for (uint8_t outputStream = 0; outputStream < static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::NumVertexStreams); outputStream++)
{
// Skip colors if they are not being morphed
if (outputStream == static_cast<uint8_t>(SkinnedMeshOutputVertexStreams::Color) && !m_shaderOptions.m_applyColorMorphTargets)
{
continue;
}
// Set the buffer offsets
const SkinnedMeshOutputVertexStreamInfo& outputStreamInfo = SkinnedMeshVertexStreamPropertyInterface::Get()->GetOutputStreamInfo(static_cast<SkinnedMeshOutputVertexStreams>(outputStream));
{
RHI::ShaderInputConstantIndex outputOffsetIndex = m_instanceSrg->FindShaderInputConstantIndex(outputStreamInfo.m_shaderResourceGroupName);
if (!outputOffsetIndex.IsValid())
{
AZ_Error("SkinnedMeshDispatchItem", false, "Failed to find shader input index for %s in the skinning compute shader per-instance SRG.", outputStreamInfo.m_shaderResourceGroupName.GetCStr());
return false;
}
// The shader has a view with 4 bytes per element
// Divide the byte offset here so it doesn't need to be done in the shader
m_instanceSrg->SetConstant(outputOffsetIndex, m_outputBufferOffsetsInBytes[outputStream] / 4);
}
}
m_instanceSrg->SetBuffer(actorInstanceBoneTransformsIndex, m_boneTransforms);
// Set the morph target related srg constants
RHI::ShaderInputConstantIndex morphPositionOffsetIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_morphTargetPositionDeltaOffset" });
// The buffer is using 32-bit integers, so divide the offset by 4 here so it doesn't have to be done in the shader
m_instanceSrg->SetConstant(morphPositionOffsetIndex, m_morphTargetInstanceMetaData.m_accumulatedPositionDeltaOffsetInBytes / 4);
RHI::ShaderInputConstantIndex morphNormalOffsetIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_morphTargetNormalDeltaOffset" });
// The buffer is using 32-bit integers, so divide the offset by 4 here so it doesn't have to be done in the shader
m_instanceSrg->SetConstant(morphNormalOffsetIndex, m_morphTargetInstanceMetaData.m_accumulatedNormalDeltaOffsetInBytes / 4);
RHI::ShaderInputConstantIndex morphTangentOffsetIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_morphTargetTangentDeltaOffset" });
// The buffer is using 32-bit integers, so divide the offset by 4 here so it doesn't have to be done in the shader
m_instanceSrg->SetConstant(morphTangentOffsetIndex, m_morphTargetInstanceMetaData.m_accumulatedTangentDeltaOffsetInBytes / 4);
RHI::ShaderInputConstantIndex morphBitangentOffsetIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_morphTargetBitangentDeltaOffset" });
// The buffer is using 32-bit integers, so divide the offset by 4 here so it doesn't have to be done in the shader
m_instanceSrg->SetConstant(morphBitangentOffsetIndex, m_morphTargetInstanceMetaData.m_accumulatedBitangentDeltaOffsetInBytes / 4);
if (m_shaderOptions.m_applyColorMorphTargets)
{
RHI::ShaderInputConstantIndex morphColorOffsetIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_morphTargetColorDeltaOffset" });
// The buffer is using 32-bit integers, so divide the offset by 4 here so it doesn't have to be done in the shader
m_instanceSrg->SetConstant(morphColorOffsetIndex, m_morphTargetInstanceMetaData.m_accumulatedColorDeltaOffsetInBytes / 4);
}
RHI::ShaderInputConstantIndex morphDeltaIntegerEncodingIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_morphTargetDeltaInverseIntegerEncoding" });
m_instanceSrg->SetConstant(morphDeltaIntegerEncodingIndex, 1.0f / m_morphTargetDeltaIntegerEncoding);
// Set the vertex count
const uint32_t vertexCount = m_inputBuffers->GetVertexCount(m_lodIndex);
RHI::ShaderInputConstantIndex numVerticesIndex;
numVerticesIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_numVertices" });
AZ_Error("SkinnedMeshInputBuffers", numVerticesIndex.IsValid(), "Failed to find shader input index for m_numVerticies in the skinning compute shader per-instance SRG.");
m_instanceSrg->SetConstant(numVerticesIndex, vertexCount);
uint32_t xThreads = 0;
uint32_t yThreads = 0;
CalculateSkinnedMeshTotalThreadsPerDimension(vertexCount, xThreads, yThreads);
// Set the total number of threads in the x dimension, so the shader can calculate the vertex index from the thread ids
RHI::ShaderInputConstantIndex totalNumberOfThreadsXIndex;
totalNumberOfThreadsXIndex = m_instanceSrg->FindShaderInputConstantIndex(Name{ "m_totalNumberOfThreadsX" });
AZ_Error("SkinnedMeshInputBuffers", totalNumberOfThreadsXIndex.IsValid(), "Failed to find shader input index for m_totalNumberOfThreadsX in the skinning compute shader per-instance SRG.");
m_instanceSrg->SetConstant(totalNumberOfThreadsXIndex, xThreads);
m_instanceSrg->Compile();
m_dispatchItem.m_uniqueShaderResourceGroup = m_instanceSrg->GetRHIShaderResourceGroup();
m_dispatchItem.m_pipelineState = m_skinningShader->AcquirePipelineState(pipelineStateDescriptor);
const auto& numThreads = m_skinningShader->GetAsset()->GetAttribute(RHI::ShaderStage::Compute, AZ::Name{ "numthreads" });
auto& arguments = m_dispatchItem.m_arguments.m_direct;
if (numThreads)
{
const auto& args = *numThreads;
arguments.m_threadsPerGroupX = args[0].type() == azrtti_typeid<int>() ? AZStd::any_cast<int>(args[0]) : 1;
arguments.m_threadsPerGroupY = args[1].type() == azrtti_typeid<int>() ? AZStd::any_cast<int>(args[1]) : 1;
arguments.m_threadsPerGroupZ = args[2].type() == azrtti_typeid<int>() ? AZStd::any_cast<int>(args[2]) : 1;
}
arguments.m_totalNumberOfThreadsX = xThreads;
arguments.m_totalNumberOfThreadsY = yThreads;
arguments.m_totalNumberOfThreadsZ = 1;
return true;
}
const RHI::DispatchItem& SkinnedMeshDispatchItem::GetRHIDispatchItem() const
{
return m_dispatchItem;
}
Data::Instance<RPI::Buffer> SkinnedMeshDispatchItem::GetBoneTransforms() const
{
return m_boneTransforms;
}
AZStd::array_view<AZ::RHI::Ptr<RHI::BufferView>> SkinnedMeshDispatchItem::GetSourceUnskinnedBufferViews() const
{
return m_inputBuffers->GetInputBufferViews(m_lodIndex);
}
AZStd::array_view<AZ::RHI::Ptr<RHI::BufferView>> SkinnedMeshDispatchItem::GetTargetSkinnedBufferViews() const
{
return m_actorInstanceBufferViews;
}
size_t SkinnedMeshDispatchItem::GetVertexCount() const
{
return aznumeric_cast<size_t>(m_inputBuffers->GetVertexCount(m_lodIndex));
}
void SkinnedMeshDispatchItem::OnShaderReinitialized(const CachedSkinnedMeshShaderOptions* cachedShaderOptions)
{
m_shaderOptionGroup = cachedShaderOptions->CreateShaderOptionGroup(m_shaderOptions);
if (!Init())
{
AZ_Error("SkinnedMeshDispatchItem", false, "Failed to re-initialize after the shader was re-loaded.");
}
}
void CalculateSkinnedMeshTotalThreadsPerDimension(uint32_t vertexCount, uint32_t& xThreads, uint32_t& yThreads)
{
const uint32_t maxVerticesPerDimension = static_cast<uint32_t>(std::numeric_limits<uint16_t>::max());
if (vertexCount > maxVerticesPerDimension * maxVerticesPerDimension)
{
AZ_Error("CalculateSkinnedMeshTotalThreadsPerDimension", false, "Vertex count '%d' exceeds maximum supported vertices '%d' for skinned meshes. Not all vertices will be rendered.", vertexCount, maxVerticesPerDimension * maxVerticesPerDimension);
xThreads = maxVerticesPerDimension;
yThreads = maxVerticesPerDimension;
return;
}
else if (vertexCount == 0)
{
AZ_Error("CalculateSkinnedMeshTotalThreadsPerDimension", false, "Cannot skin mesh with 0 vertices.");
xThreads = 0;
yThreads = 0;
return;
}
// Get the minimum number of threads in the y dimension needed to cover all the vertices in the mesh
yThreads = vertexCount % maxVerticesPerDimension != 0 ? vertexCount / maxVerticesPerDimension + 1 : vertexCount / maxVerticesPerDimension;
// Divide the total number of threads across y dimensions, rounding the number of xThreads up to cover any remainder
xThreads = 1 + ((vertexCount - 1) / yThreads);
}
} // namespace Render
} // namespace AZ