You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshDispatchItem.cpp

282 lines
16 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#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 perInstanceSrgLayout = m_skinningShader->FindShaderResourceGroupLayout(AZ::Name{ "InstanceSrg" });
if (!perInstanceSrgLayout)
{
AZ_Error("SkinnedMeshDispatchItem", false, "Failed to get shader resource group layout");
return false;
}
m_instanceSrg = RPI::ShaderResourceGroup::Create(m_skinningShader->GetAsset(), m_skinningShader->GetSupervariantIndex(), perInstanceSrgLayout->GetName());
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 = static_cast<uint16_t>(args[0].type() == azrtti_typeid<int>() ? AZStd::any_cast<int>(args[0]) : 1);
arguments.m_threadsPerGroupY = static_cast<uint16_t>(args[1].type() == azrtti_typeid<int>() ? AZStd::any_cast<int>(args[1]) : 1);
arguments.m_threadsPerGroupZ = static_cast<uint16_t>(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