2
0
BearishSun 9 жил өмнө
parent
commit
7c3620dd46

+ 31 - 11
Source/BansheeCore/Include/BsAnimation.h

@@ -31,8 +31,9 @@ namespace BansheeEngine
 	enum class AnimDirtyStateFlag
 	{
 		Clean = 0,
-		Value = 1,
-		Layout = 2
+		Value = 1 << 0,
+		Layout = 1 << 1,
+		Skeleton = 1 << 2
 	};
 
 	typedef Flags<AnimDirtyStateFlag> AnimDirtyState;
@@ -55,11 +56,19 @@ namespace BansheeEngine
 	/** Internal information about a single playing animation clip within Animation. */
 	struct PlayingClipInfo
 	{
-		PlayingClipInfo() { }
+		PlayingClipInfo();
 		PlayingClipInfo(const HAnimationClip& clip);
 
 		HAnimationClip clip;
 		AnimationClipState state;
+
+		/** 
+		 * Version of the animation curves used by the AnimationProxy. Used to detecting the internal animation curves
+		 * changed. 
+		 */
+		UINT64 curveVersion; 
+		UINT32 layerIdx; /**< Layer index this clip belongs to in AnimationProxy structure. */
+		UINT32 stateIdx; /**< State index this clip belongs to in AnimationProxy structure. */
 	};
 
 	/** Represents a copy of the Animation data for use specifically on the animation thread. */
@@ -69,30 +78,41 @@ namespace BansheeEngine
 		~AnimationProxy();
 
 		/** 
-		 * Updates the proxy data with a new skeleton and clips. Very expensive update operation.
+		 * Rebuilds the internal proxy data according to the newly assigned skeleton and clips. This should be called
+		 * whenever the animation skeleton changes.
+		 *
+		 * @param[in]		skeleton	New skeleton to assign to the proxy.
+		 * @param[in, out]	clipInfos	Potentially new clip infos that will be used for rebuilding the proxy. Once the
+		 *								method completes clip info layout and state indices will be populated for 
+		 *								further use in the update*() methods.
 		 *
 		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
 		 */
-		void updateSkeleton(const SPtr<Skeleton>& skeleton, const Vector<PlayingClipInfo>& clipInfos);
+		void rebuild(const SPtr<Skeleton>& skeleton, Vector<PlayingClipInfo>& clipInfos);
 
 		/** 
-		 * Updates the proxy data with new clips. Very expensive update operation.
+		 * Rebuilds the internal proxy data according to the newly clips. This should be called whenever clips are added
+		 * or removed, or clip layout indices change.
+		 *
+		 * @param[in, out]	clipInfos	New clip infos that will be used for rebuilding the proxy. Once the method completes
+		 *								clip info layout and state indices will be populated for further use in the
+		 *								update*() methods.
 		 *
 		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
 		 */
-		void updateLayout(const Vector<PlayingClipInfo>& clipInfos);
+		void rebuild(Vector<PlayingClipInfo>& clipInfos);
 
 		/** 
 		 * Updates the proxy data with new information about the clips. Caller must guarantee that clip layout didn't 
-		 * change. Fairly cheap update operation.
+		 * change since the last call to rebuild().
 		 *
 		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
 		 */
 		void updateValues(const Vector<PlayingClipInfo>& clipInfos);
 
 		/** 
-		 * Updates the proxy data with new clip times. Caller must guarantee that clip layout didn't change. Very cheap
-		 * update operation.
+		 * Updates the proxy data with new clip times. Caller must guarantee that clip layout didn't change since the last
+		 * call to rebuild().
 		 *
 		 * @note	Should be called from the sim thread when the caller is sure the animation thread is not using it.
 		 */
@@ -217,7 +237,7 @@ namespace BansheeEngine
 		Vector<PlayingClipInfo> mPlayingClips;
 
 		// Animation thread only
-		AnimationProxy mAnimProxy;
+		SPtr<AnimationProxy> mAnimProxy;
 	};
 
 	/** @} */

+ 39 - 0
Source/BansheeCore/Include/BsAnimationManager.h

@@ -7,6 +7,8 @@
 
 namespace BansheeEngine
 {
+	struct AnimationProxy;
+
 	/** @addtogroup Animation-Internal
 	 *  @{
 	 */
@@ -20,6 +22,29 @@ namespace BansheeEngine
 	public:
 		AnimationManager();
 
+		/** Pauses or resumes the animation evaluation. */
+		void setPaused(bool paused);
+
+		/** 
+		 * Determines how often to evaluate animations. If rendering is not running at adequate framerate the animation
+		 * could end up being evaluated less times than specified here.
+		 *
+		 * @param[in]	fps		Number of frames per second to evaluate the animation. Default is 60.
+		 */
+		void setUpdateRate(UINT32 fps);
+
+		/** 
+		 * Synchronizes animation data from the animation thread with the scene objects. Should be called before component
+		 * updates are sent. 
+		 */
+		void preUpdate();
+
+		/**
+		 * Synchronizes animation data to the animation thread, advances animation time and queues new animation evaluation
+		 * task.
+		 */
+		void postUpdate();
+
 	private:
 		friend class Animation;
 
@@ -31,8 +56,22 @@ namespace BansheeEngine
 		/** Unregisters an animation with the specified ID. Must be called before an Animation is destroyed. */
 		void unregisterAnimation(UINT64 id);
 
+		/** Worker method ran on the animation thread that evaluates all animation at the provided time. */
+		void evaluateAnimation();
+
 		UINT64 mNextId;
 		UnorderedMap<UINT64, Animation*> mAnimations;
+		
+		float mUpdateRate;
+		float mAnimationTime;
+		float mLastAnimationUpdateTime;
+		float mNextAnimationUpdateTime;
+		bool mPaused;
+
+		bool mWorkerRunning;
+		SPtr<Task> mAnimationWorker;
+
+		Vector<SPtr<AnimationProxy>> mProxies;
 	};
 
 	/** @} */

+ 79 - 24
Source/BansheeCore/Source/BsAnimation.cpp

@@ -6,8 +6,12 @@
 
 namespace BansheeEngine
 {
+	PlayingClipInfo::PlayingClipInfo()
+		:layerIdx(0), stateIdx(0)
+	{ }
+
 	PlayingClipInfo::PlayingClipInfo(const HAnimationClip& clip)
-		:clip(clip)
+		:clip(clip), layerIdx(0), stateIdx(0)
 	{ }
 
 	AnimationProxy::AnimationProxy()
@@ -20,15 +24,19 @@ namespace BansheeEngine
 			bs_free(layers);
 	}
 
-	void AnimationProxy::updateSkeleton(const SPtr<Skeleton>& skeleton, const Vector<PlayingClipInfo>& clipInfos)
+	void AnimationProxy::rebuild(const SPtr<Skeleton>& skeleton, Vector<PlayingClipInfo>& clipInfos)
 	{
 		this->skeleton = skeleton;
-		pose = SkeletonPose(skeleton->getNumBones());
 
-		updateLayout(clipInfos);
+		if (skeleton != nullptr)
+			pose = SkeletonPose(skeleton->getNumBones());
+		else
+			pose = SkeletonPose();
+
+		rebuild(clipInfos);
 	}
 
-	void AnimationProxy::updateLayout(const Vector<PlayingClipInfo>& clipInfos)
+	void AnimationProxy::rebuild(Vector<PlayingClipInfo>& clipInfos)
 	{
 		if (layers != nullptr)
 			bs_free(layers);
@@ -62,7 +70,12 @@ namespace BansheeEngine
 
 			UINT32 numLayers = (UINT32)tempLayers.size();
 			UINT32 numClips = (UINT32)clipInfos.size();
-			UINT32 numBones = skeleton->getNumBones();
+			UINT32 numBones;
+			
+			if (skeleton != nullptr)
+				numBones = skeleton->getNumBones();
+			else
+				numBones = 0;
 
 			UINT32 layersSize = sizeof(AnimationStateLayer) * numLayers;
 			UINT32 clipsSize = sizeof(AnimationState) * numClips;
@@ -77,6 +90,8 @@ namespace BansheeEngine
 			data += clipsSize;
 
 			AnimationCurveMapping* boneMappings = (AnimationCurveMapping*)data;
+			
+			UINT32 curLayerIdx = 0;
 			UINT32 curStateIdx = 0;
 
 			for(auto& layer : tempLayers)
@@ -84,6 +99,7 @@ namespace BansheeEngine
 				layer.states = &states[curStateIdx];
 				layer.numStates = 0;
 
+				UINT32 localStateIdx = 0;
 				for(auto& clipInfo : clipInfos)
 				{
 					if (clipInfo.state.layer != layer.index)
@@ -92,7 +108,6 @@ namespace BansheeEngine
 					new (&states[curStateIdx]) AnimationState();
 					AnimationState& state = states[curStateIdx];
 					state.curves = clipInfo.clip->getCurves();
-					state.boneToCurveMapping = &boneMappings[curStateIdx * numBones];
 					state.weight = clipInfo.state.weight;
 					state.loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
 
@@ -100,12 +115,25 @@ namespace BansheeEngine
 					state.rotationEval.time = clipInfo.state.time;
 					state.scaleEval.time = clipInfo.state.time;
 
-					clipInfo.clip->getBoneMapping(*skeleton, state.boneToCurveMapping);
+					clipInfo.layerIdx = curLayerIdx;
+					clipInfo.stateIdx = localStateIdx;
+					clipInfo.curveVersion = clipInfo.clip->getVersion();
+
+					if (skeleton != nullptr)
+					{
+						state.boneToCurveMapping = &boneMappings[curStateIdx * numBones];
+						clipInfo.clip->getBoneMapping(*skeleton, state.boneToCurveMapping);
+					}
+					else
+						state.boneToCurveMapping = nullptr;
 
 					layer.numStates++;
 					curStateIdx++;
+					localStateIdx++;
 				}
 
+				curLayerIdx++;
+
 				// Must be larger than zero otherwise the layer.states pointer will point to data held by some other layer
 				assert(layer.numStates > 0);
 			}			
@@ -115,17 +143,35 @@ namespace BansheeEngine
 
 	void AnimationProxy::updateValues(const Vector<PlayingClipInfo>& clipInfos)
 	{
-		// TODO
+		for(auto& clipInfo : clipInfos)
+		{
+			AnimationState& state = layers[clipInfo.layerIdx].states[clipInfo.stateIdx];
+
+			state.loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
+			state.weight = clipInfo.state.weight;
+
+			state.positionEval.time = clipInfo.state.time;
+			state.rotationEval.time = clipInfo.state.time;
+			state.scaleEval.time = clipInfo.state.time;
+		}
 	}
 
 	void AnimationProxy::updateTime(const Vector<PlayingClipInfo>& clipInfos)
 	{
-		// TODO
+		for (auto& clipInfo : clipInfos)
+		{
+			AnimationState& state = layers[clipInfo.layerIdx].states[clipInfo.stateIdx];
+
+			state.positionEval.time = clipInfo.state.time;
+			state.rotationEval.time = clipInfo.state.time;
+			state.scaleEval.time = clipInfo.state.time;
+		}
 	}
 
 	Animation::Animation()
-		:mDefaultWrapMode(AnimWrapMode::Loop), mDefaultSpeed(1.0f), mDirty(AnimDirtyStateFlag::Clean)
+		:mDefaultWrapMode(AnimWrapMode::Loop), mDefaultSpeed(1.0f), mDirty(AnimDirtyStateFlag::Skeleton)
 	{
+		mAnimProxy = bs_shared_ptr_new<AnimationProxy>();
 		mId = AnimationManager::instance().registerAnimation(this);
 	}
 
@@ -137,7 +183,7 @@ namespace BansheeEngine
 	void Animation::setSkeleton(const SPtr<Skeleton>& skeleton)
 	{
 		mSkeleton = skeleton;
-		mDirty |= AnimDirtyStateFlag::Layout;
+		mDirty |= AnimDirtyStateFlag::Skeleton;
 	}
 
 	void Animation::setWrapMode(AnimWrapMode wrapMode)
@@ -273,20 +319,29 @@ namespace BansheeEngine
 
 	void Animation::updateAnimProxy(float timeDelta)
 	{
-		// TODO - Ensure that animation task is not running (also perhaps another memory barrier before start?)
-		
-		// TODO - Skeleton might not be available but I'm assuming it will be in the Proxy (and possibly at other places)
-		//   - In order to allow TRS animation without a skeleton, always provide a single-bone skeleton?
+		// Check if any of the clip curves are dirty and advance time
+		for (auto& clipInfo : mPlayingClips)
+		{
+			clipInfo.state.time += timeDelta * clipInfo.state.speed;
 
-		// TODO - Check if any of the clip curves are dirty
+			if (clipInfo.curveVersion != clipInfo.clip->getVersion())
+				mDirty |= AnimDirtyStateFlag::Layout;
+		}
 
-		// TODO - If layout dirty, do full rebuild
-		//      - If value dirty, update all values
-		//      - If nothing dirty, just advance the times
+		if((UINT32)mDirty == 0) // Clean
+		{
+			mAnimProxy->updateTime(mPlayingClips);
+		}
+		else
+		{
+			if (mDirty.isSet(AnimDirtyStateFlag::Skeleton))
+				mAnimProxy->rebuild(mSkeleton, mPlayingClips);
+			else if (mDirty.isSet(AnimDirtyStateFlag::Layout))
+				mAnimProxy->rebuild(mPlayingClips);
+			else if (mDirty.isSet(AnimDirtyStateFlag::Value))
+				mAnimProxy->updateValues(mPlayingClips);
+		}
 
-		// TODO - Make sure anim manager performs a memory barrier after these calls
+		mDirty = AnimDirtyState();
 	}
-
-	
-	// TODO - When non-looping clips reach the end make sure to remove them from the playing clips list
 }

+ 78 - 1
Source/BansheeCore/Source/BsAnimationManager.cpp

@@ -1,13 +1,90 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsAnimationManager.h"
+#include "BsAnimation.h"
+#include "BsTaskScheduler.h"
+#include "BsTime.h"
 
 namespace BansheeEngine
 {
 	AnimationManager::AnimationManager()
-		:mNextId(1)
+		: mNextId(1), mUpdateRate(1.0f / 60.0f), mAnimationTime(0.0f), mLastAnimationUpdateTime(0.0f)
+		, mNextAnimationUpdateTime(0.0f), mPaused(false), mWorkerRunning(false)
 	{
+		mAnimationWorker = Task::create("Animation", std::bind(&AnimationManager::evaluateAnimation, this));
+	}
+
+	void AnimationManager::setPaused(bool paused)
+	{
+		mPaused = paused;
+	}
+
+	void AnimationManager::setUpdateRate(UINT32 fps)
+	{
+		if (fps == 0)
+			fps = 1;
+
+		mUpdateRate = 1.0f / fps;
+	}
+
+	void AnimationManager::preUpdate()
+	{
+		if (mPaused || !mWorkerRunning)
+			return;
+
+		mAnimationWorker->wait();
+
+		// Make sure we don't load obsolete skeletal pose and other evaluation ouputs written by the animation thread
+		std::atomic_thread_fence(std::memory_order_acquire);
+
+		// TODO - Write TRS animation results to relevant SceneObjects
+		// TODO - Transfer generic curve evaluated data back to Animation
+
+		mWorkerRunning = false;
+	}
+
+	void AnimationManager::postUpdate()
+	{
+		if (mPaused)
+			return;
+
+		mAnimationTime += gTime().getFrameDelta();
+		if (mAnimationTime < mNextAnimationUpdateTime)
+			return;
+
+		mNextAnimationUpdateTime = Math::floor(mAnimationTime / mUpdateRate) * mUpdateRate + mUpdateRate;
+
+		float timeDelta = mAnimationTime - mLastAnimationUpdateTime;
+		mLastAnimationUpdateTime = mAnimationTime;
+
+		for (auto& anim : mAnimations)
+			anim.second->updateAnimProxy(timeDelta);
+
+		// TODO - Gather a list of all anim proxies?
+
+		// Make sure thread finishes writing all changes to the anim proxies as they will be read by the animation thread
+		std::atomic_thread_fence(std::memory_order_release);
+		
+		// Note: Animation thread will trigger about the same time as the core thread. The core thread will need to wait
+		// until animation thread finishes, which might end up blocking it (and losing the multi-threading performance). 
+		// Consider delaying displayed animation for a single frame or pre-calculating animations (by advancing time the
+		// previous frame) for non-dirty animations.
+		TaskScheduler::instance().addTask(mAnimationWorker);
+		mWorkerRunning = true;
+	}
+
+	void AnimationManager::evaluateAnimation()
+	{
+		// Make sure we don't load obsolete anim proxy data written by the simulation thread
+		std::atomic_thread_fence(std::memory_order_acquire);
+
+		// TODO - Evaluate skeletal poses for clips that have a skeleton
+		// TODO - Evaluate TRS for individual scene objects that have no skeleton
+		// TODO - Evaluate generic curves
 
+		// Make sure the thread finishes writing skeletal pose and other evaluation outputs as they will be read by sim and
+		// core threads
+		std::atomic_thread_fence(std::memory_order_release);
 	}
 
 	UINT64 AnimationManager::registerAnimation(Animation* anim)

+ 5 - 0
Source/BansheeCore/Source/BsCoreApplication.cpp

@@ -47,6 +47,7 @@
 #include "BsPhysics.h"
 #include "BsAudioManager.h"
 #include "BsAudio.h"
+#include "BsAnimationManager.h"
 
 namespace BansheeEngine
 {
@@ -78,6 +79,7 @@ namespace BansheeEngine
 
 		// This must be done after all resources are released since it will unload the physics plugin, and some resources
 		// might be instances of types from that plugin.
+		AnimationManager::shutDown();
 		PhysicsManager::shutDown();
 		AudioManager::shutDown();
 
@@ -160,6 +162,7 @@ namespace BansheeEngine
 		Importer::startUp();
 		AudioManager::startUp(mStartUpDesc.audio);
 		PhysicsManager::startUp(mStartUpDesc.physics, isEditor());
+		AnimationManager::startUp();
 
 		for (auto& importerName : mStartUpDesc.importers)
 			loadPlugin(importerName);
@@ -214,12 +217,14 @@ namespace BansheeEngine
 			RenderWindowManager::instance()._update(); 
 			gInput()._triggerCallbacks();
 			gDebug()._triggerCallbacks();
+			AnimationManager::instance().preUpdate();
 
 			preUpdate();
 
 			PROFILE_CALL(gCoreSceneManager()._update(), "SceneManager");
 			gAudio()._update();
 			gPhysics().update();
+			AnimationManager::instance().postUpdate();
 
 			// Update plugins
 			for (auto& pluginUpdateFunc : mPluginUpdateFunctions)

+ 2 - 0
Source/BansheeCore/Source/BsSkeleton.cpp

@@ -99,6 +99,8 @@ namespace BansheeEngine
 
 	void Skeleton::getPose(SkeletonPose& pose, const AnimationStateLayer* layers, UINT32 numLayers)
 	{
+		// Note: If more performance is required this method could be optimized with vector instructions
+
 		assert(pose.numBones == mNumBones);
 
 		for(UINT32 i = 0; i < mNumBones; i++)