From f918b12b5acfd790caebddec79807bd8eb3332f1 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Date: Tue, 9 Nov 2021 13:07:20 +0100 Subject: [PATCH] EMotion FX: Taskify dual quat skinning deformer (#5368) * Moved from job system to task graph for the multi-threaded dual quaternion skinning deformer. * Prepare the task graph at init time and reuse it at runtime. Signed-off-by: Benjamin Jillich --- .../EMotionFX/Source/DualQuatSkinDeformer.cpp | 75 ++++++++++++++----- .../EMotionFX/Source/DualQuatSkinDeformer.h | 3 + 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp index 2d5d8e2a46..afd570c102 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.cpp @@ -27,6 +27,8 @@ namespace EMotionFX DualQuatSkinDeformer::DualQuatSkinDeformer(Mesh* mesh) : MeshDeformer(mesh) { + AZ::TaskGraphActiveInterface* taskGraphActiveInterface = AZ::Interface::Get(); + m_useTaskGraph = taskGraphActiveInterface && taskGraphActiveInterface->IsTaskGraphActive(); } DualQuatSkinDeformer::~DualQuatSkinDeformer() @@ -79,9 +81,8 @@ namespace EMotionFX { const Actor* actor = actorInstance->GetActor(); const Pose* pose = actorInstance->GetTransformData()->GetCurrentPose(); - const uint32 numVertices = m_mesh->GetNumVertices(); - // pre-calculate the skinning matrices + // Calculate the skinning matrices based on the current pose. for (BoneInfo& boneInfo : m_bones) { const size_t nodeIndex = boneInfo.m_nodeNr; @@ -89,27 +90,38 @@ namespace EMotionFX boneInfo.m_dualQuat.FromRotationTranslation(skinTransform.m_rotation, skinTransform.m_position); } - AZ::JobCompletion jobCompletion; - - // Split up the skinned vertices into batches. - const AZ::u32 numBatches = aznumeric_caster(ceilf(aznumeric_cast(numVertices) / aznumeric_cast(s_numVerticesPerBatch))); - for (AZ::u32 batchIndex = 0; batchIndex < numBatches; ++batchIndex) + if (m_useTaskGraph) { - const AZ::u32 startVertex = batchIndex * s_numVerticesPerBatch; - const AZ::u32 endVertex = AZStd::min(startVertex + s_numVerticesPerBatch, numVertices); - - // Create a job for every batch and skin them simultaneously. - AZ::JobContext* jobContext = nullptr; - AZ::Job* job = AZ::CreateJobFunction([this, startVertex, endVertex]() - { - SkinRange(m_mesh, startVertex, endVertex, m_bones); - }, /*isAutoDelete=*/true, jobContext); - - job->SetDependent(&jobCompletion); - job->Start(); + // Skin the vertices by executing the task graph. + AZ::TaskGraphEvent finishedEvent; + m_taskGraph.Submit(&finishedEvent); + finishedEvent.Wait(); } + else + { + AZ::JobCompletion jobCompletion; - jobCompletion.StartAndWaitForCompletion(); + // Split up the skinned vertices into batches. + const uint32 numVertices = m_mesh->GetNumVertices(); + const AZ::u32 numBatches = aznumeric_caster(ceilf(aznumeric_cast(numVertices) / aznumeric_cast(s_numVerticesPerBatch))); + for (AZ::u32 batchIndex = 0; batchIndex < numBatches; ++batchIndex) + { + const AZ::u32 startVertex = batchIndex * s_numVerticesPerBatch; + const AZ::u32 endVertex = AZStd::min(startVertex + s_numVerticesPerBatch, numVertices); + + // Create a job for every batch and skin them simultaneously. + AZ::JobContext* jobContext = nullptr; + AZ::Job* job = AZ::CreateJobFunction([this, startVertex, endVertex]() + { + SkinRange(m_mesh, startVertex, endVertex, m_bones); + }, /*isAutoDelete=*/true, jobContext); + + job->SetDependent(&jobCompletion); + job->Start(); + } + + jobCompletion.StartAndWaitForCompletion(); + } } void DualQuatSkinDeformer::SkinRange(Mesh* mesh, AZ::u32 startVertex, AZ::u32 endVertex, const AZStd::vector& boneInfos) @@ -340,5 +352,28 @@ namespace EMotionFX } } } + + if (m_useTaskGraph) + { + // Prepare the task graph + // Split up the to be skinned vertices into batches. As the mesh does not change at runtime, the task graph can + // be prepared at init time and be reused at runtime. + const uint32 numVertices = m_mesh->GetNumVertices(); + const AZ::u32 numBatches = aznumeric_caster(ceilf(aznumeric_cast(numVertices) / aznumeric_cast(s_numVerticesPerBatch))); + for (AZ::u32 batchIndex = 0; batchIndex < numBatches; ++batchIndex) + { + const AZ::u32 startVertex = batchIndex * s_numVerticesPerBatch; + const AZ::u32 endVertex = AZStd::min(startVertex + s_numVerticesPerBatch, numVertices); + + // Create a task for every batch and skin them simultaneously. + AZ::TaskDescriptor taskDescriptor{"DualQuatSkinRange", "Animation"}; + m_taskGraph.AddTask( + taskDescriptor, + [this, startVertex, endVertex]() + { + SkinRange(m_mesh, startVertex, endVertex, m_bones); + }); + } + } } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h index 434f2920ee..95312c5b83 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/DualQuatSkinDeformer.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include "EMotionFXConfig.h" #include @@ -138,6 +139,8 @@ namespace EMotionFX //! Number of vertices per batch/job used for multi-threaded software skinning. static constexpr AZ::u32 s_numVerticesPerBatch = 10000; + AZ::TaskGraph m_taskGraph; + bool m_useTaskGraph = true; /** * Default constructor.