Add async animations updating via Task Graph
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user