Add async animations updating via Task Graph

This commit is contained in:
Wojtek Figat
2021-06-12 23:18:04 +02:00
parent 360f498e47
commit 4a92850d9a
4 changed files with 127 additions and 68 deletions

View File

@@ -6,8 +6,7 @@
#include "Engine/Level/Actors/AnimatedModel.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
Array<AnimatedModel*> 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<AnimatedModel*> UpdateList;
TaskGraphSystem* Animations::System = nullptr;
Delegate<Asset*, ScriptingObject*, uint32, uint32> 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<AnimationsSystem>();
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<void(int32)> job;
job.Bind<AnimationsSystem, &AnimationsSystem::Job>(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)

View File

@@ -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);
/// <summary>
/// The system for Animations update.
/// </summary>
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<Asset*, ScriptingObject*, uint32, uint32> DebugFlow;

View File

@@ -113,16 +113,6 @@ void AnimatedModel::PreInitSkinningData()
UpdateSockets();
}
void AnimatedModel::UpdateSockets()
{
for (int32 i = 0; i < Children.Count(); i++)
{
auto socket = dynamic_cast<BoneSocket*>(Children[i]);
if (socket)
socket->UpdateTransformation();
}
}
void AnimatedModel::GetCurrentPose(Array<Matrix>& 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<BoneSocket*>(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();

View File

@@ -15,7 +15,7 @@
API_CLASS() class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(AnimatedModel);
friend class AnimationsService;
friend class AnimationsSystem;
public:
/// <summary>
@@ -306,6 +306,8 @@ private:
void UpdateLocalBounds();
void UpdateBounds();
void UpdateSockets();
void OnAnimationUpdated_Async();
void OnAnimationUpdated_Sync();
void OnAnimationUpdated();
void OnSkinnedModelChanged();