b9824ed172
* Updated all array_view uses with the C++20 span. The updates were done in the following order 1. `AZStd::array_view<([^>].+)\* ?>` -> `AZStd::span<\1 const>` 2. `AZStd::array_view<(?:const )(.+)>` -> `AZStd::span<const \1>` 3. `AZStd::array_view` -> `AZStd::span` Removed the implementation of array_view. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Added missing whitespace between `const` and the typename for spans. Updated the ShaderTest comparison of the ShaderResourceGroupLayout span to compare the sizes as well Updated comments on some of the methods that stated that they return "an array" to mention they return "a span". Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
280 lines
15 KiB
C++
280 lines
15 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/RPI.Public/RPIUtils.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);
|
|
|
|
auto& arguments = m_dispatchItem.m_arguments.m_direct;
|
|
const auto outcome = RPI::GetComputeShaderNumThreads(m_skinningShader->GetAsset(), arguments);
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
AZ_Error("SkinnedMeshInputBuffers", false, outcome.GetError().c_str());
|
|
}
|
|
|
|
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::span<const AZ::RHI::Ptr<RHI::BufferView>> SkinnedMeshDispatchItem::GetSourceUnskinnedBufferViews() const
|
|
{
|
|
return m_inputBuffers->GetInputBufferViews(m_lodIndex);
|
|
}
|
|
|
|
AZStd::span<const 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
|