diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index 037c87a5f..179e710e4 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -6,8 +6,7 @@ #include "Engine/Level/Actors/AnimatedModel.h" #include "Engine/Engine/Time.h" #include "Engine/Engine/EngineService.h" - -Array UpdateList; +#include "Engine/Threading/TaskGraph.h" class AnimationsService : public EngineService { @@ -18,70 +17,110 @@ public: { } - void Update() override; + bool Init() override; void Dispose() override; }; +class AnimationsSystem : public TaskGraphSystem +{ +public: + float DeltaTime, UnscaledDeltaTime, Time, UnscaledTime; + void Job(int32 index); + void Execute(TaskGraph* graph) override; + void PostExecute(TaskGraph* graph) override; +}; + AnimationsService AnimationManagerInstance; +Array UpdateList; +TaskGraphSystem* Animations::System = nullptr; Delegate Animations::DebugFlow; -void AnimationsService::Update() +bool AnimationsService::Init() { - PROFILE_CPU_NAMED("Animations"); - - // TODO: implement the thread jobs pipeline to run set of tasks at once (use it for multi-threaded rendering and animations evaluation) - - const auto& tickData = Time::Update; - const float deltaTime = tickData.DeltaTime.GetTotalSeconds(); - const float unscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds(); - const float time = tickData.Time.GetTotalSeconds(); - const float unscaledTime = tickData.UnscaledTime.GetTotalSeconds(); - - for (int32 i = 0; i < UpdateList.Count(); i++) - { - auto animatedModel = UpdateList[i]; - if (animatedModel->SkinnedModel == nullptr || !animatedModel->SkinnedModel->IsLoaded()) - continue; - - // Prepare skinning data - animatedModel->SetupSkinningData(); - - // Update the animation graph and the skinning - auto graph = animatedModel->AnimationGraph.Get(); - if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(animatedModel->SkinnedModel) -#if USE_EDITOR - && graph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes -#endif - ) - { -#if USE_EDITOR - // Lock in editor only (more reloads during asset live editing) - ScopeLock lock(animatedModel->AnimationGraph->Locker); -#endif - - // Animation delta time can be based on a time since last update or the current delta - float dt = animatedModel->UseTimeScale ? deltaTime : unscaledDeltaTime; - float t = animatedModel->UseTimeScale ? time : unscaledTime; - const float lastUpdateTime = animatedModel->GraphInstance.LastUpdateTime; - if (lastUpdateTime > 0 && t > lastUpdateTime) - { - dt = t - lastUpdateTime; - } - animatedModel->GraphInstance.LastUpdateTime = t; - - // Evaluate animated nodes pose - graph->GraphExecutor.Update(animatedModel->GraphInstance, dt); - - // Update gameplay - animatedModel->OnAnimationUpdated(); - } - } - UpdateList.Clear(); + Animations::System = New(); + Engine::UpdateGraph->AddSystem(Animations::System); + return false; } void AnimationsService::Dispose() { UpdateList.Resize(0); + SAFE_DELETE(Animations::System); +} + +void AnimationsSystem::Job(int32 index) +{ + PROFILE_CPU_NAMED("Animations.Job"); + auto animatedModel = UpdateList[index]; + auto skinnedModel = animatedModel->SkinnedModel.Get(); + auto graph = animatedModel->AnimationGraph.Get(); + if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(skinnedModel) +#if USE_EDITOR + && graph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes +#endif + ) + { + // Prepare skinning data + animatedModel->SetupSkinningData(); + + // Animation delta time can be based on a time since last update or the current delta + float dt = animatedModel->UseTimeScale ? DeltaTime : UnscaledDeltaTime; + float t = animatedModel->UseTimeScale ? Time : UnscaledTime; + const float lastUpdateTime = animatedModel->GraphInstance.LastUpdateTime; + if (lastUpdateTime > 0 && t > lastUpdateTime) + { + dt = t - lastUpdateTime; + } + animatedModel->GraphInstance.LastUpdateTime = t; + + // Evaluate animated nodes pose + graph->GraphExecutor.Update(animatedModel->GraphInstance, dt); + + // Update gameplay + animatedModel->OnAnimationUpdated_Async(); + } +} + +void AnimationsSystem::Execute(TaskGraph* graph) +{ + if (UpdateList.Count() == 0) + return; + + // Setup data for async update + const auto& tickData = Time::Update; + DeltaTime = tickData.DeltaTime.GetTotalSeconds(); + UnscaledDeltaTime = tickData.UnscaledDeltaTime.GetTotalSeconds(); + Time = tickData.Time.GetTotalSeconds(); + UnscaledTime = tickData.UnscaledTime.GetTotalSeconds(); + + // Schedule work to update all animated models in async + Function job; + job.Bind(this); + graph->DispatchJob(job, UpdateList.Count()); +} + +void AnimationsSystem::PostExecute(TaskGraph* graph) +{ + PROFILE_CPU_NAMED("Animations.PostExecute"); + + // Update gameplay + for (int32 index = 0; index < UpdateList.Count(); index++) + { + auto animatedModel = UpdateList[index]; + auto skinnedModel = animatedModel->SkinnedModel.Get(); + auto animGraph = animatedModel->AnimationGraph.Get(); + if (animGraph && animGraph->IsLoaded() && animGraph->Graph.CanUseWithSkeleton(skinnedModel) +#if USE_EDITOR + && animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes +#endif + ) + { + animatedModel->OnAnimationUpdated_Sync(); + } + } + + // Cleanup + UpdateList.Clear(); } void Animations::AddToUpdate(AnimatedModel* obj) diff --git a/Source/Engine/Animations/Animations.h b/Source/Engine/Animations/Animations.h index 8b5b79c67..014104eac 100644 --- a/Source/Engine/Animations/Animations.h +++ b/Source/Engine/Animations/Animations.h @@ -5,6 +5,7 @@ #include "Engine/Scripting/ScriptingType.h" #include "Engine/Core/Delegate.h" +class TaskGraphSystem; class AnimatedModel; class Asset; @@ -15,6 +16,11 @@ API_CLASS(Static) class FLAXENGINE_API Animations { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Content); + /// + /// The system for Animations update. + /// + API_FIELD(ReadOnly) static TaskGraphSystem* System; + #if USE_EDITOR // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. Args are: anim graph asset, animated object, node id, box id API_EVENT() static Delegate DebugFlow; diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 701df3d6e..45bc701c6 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -113,16 +113,6 @@ void AnimatedModel::PreInitSkinningData() UpdateSockets(); } -void AnimatedModel::UpdateSockets() -{ - for (int32 i = 0; i < Children.Count(); i++) - { - auto socket = dynamic_cast(Children[i]); - if (socket) - socket->UpdateTransformation(); - } -} - void AnimatedModel::GetCurrentPose(Array& nodesTransformation, bool worldSpace) const { nodesTransformation = GraphInstance.NodesPose; @@ -451,9 +441,19 @@ void AnimatedModel::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void AnimatedModel::OnAnimationUpdated() +void AnimatedModel::UpdateSockets() { - ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated"); + for (int32 i = 0; i < Children.Count(); i++) + { + auto socket = dynamic_cast(Children[i]); + if (socket) + socket->UpdateTransformation(); + } +} + +void AnimatedModel::OnAnimationUpdated_Async() +{ + // Update asynchronous stuff auto& skeleton = SkinnedModel->Skeleton; // Copy pose from the master @@ -482,12 +482,24 @@ void AnimatedModel::OnAnimationUpdated() } UpdateBounds(); + _blendShapes.Update(SkinnedModel.Get()); +} + +void AnimatedModel::OnAnimationUpdated_Sync() +{ + // Update synchronous stuff UpdateSockets(); ApplyRootMotion(GraphInstance.RootMotion); - _blendShapes.Update(SkinnedModel.Get()); AnimationUpdated(); } +void AnimatedModel::OnAnimationUpdated() +{ + ANIM_GRAPH_PROFILE_EVENT("OnAnimationUpdated"); + OnAnimationUpdated_Async(); + OnAnimationUpdated_Sync(); +} + void AnimatedModel::OnSkinnedModelChanged() { Entries.Release(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 8c9023d23..771833f2c 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -15,7 +15,7 @@ API_CLASS() class FLAXENGINE_API AnimatedModel : public ModelInstanceActor { DECLARE_SCENE_OBJECT(AnimatedModel); - friend class AnimationsService; + friend class AnimationsSystem; public: /// @@ -306,6 +306,8 @@ private: void UpdateLocalBounds(); void UpdateBounds(); void UpdateSockets(); + void OnAnimationUpdated_Async(); + void OnAnimationUpdated_Sync(); void OnAnimationUpdated(); void OnSkinnedModelChanged();