void RemoveIdle(CallbackBase* callback);
void RunIdles();
+ void RequestUpdateOnce();
+
static Integration::Scene GetScene(Dali::Window window);
Dali::RenderSurfaceInterface& GetSurface();
mReturnCallbacks.Swap(reusedCallbacks);
}
+void Adaptor::RequestUpdateOnce()
+{
+ if(mTestApplication)
+ {
+ auto scene = mTestApplication->GetScene();
+ if(scene)
+ {
+ tet_printf("Adaptor::RequestUpdateOnce()\n");
+ scene.KeepRendering(0.0f);
+ }
+ }
+}
+
Dali::RenderSurfaceInterface& Adaptor::GetSurface()
{
DALI_ASSERT_ALWAYS(!mWindows.empty());
.Add("stopBehavior", DevelImageVisual::StopBehavior::FIRST_FRAME)
.Add("loopingMode", DevelImageVisual::LoopingMode::AUTO_REVERSE)
.Add("redrawInScalingDown", false)
+ .Add("enableFrameCache", false)
+ .Add("notifyAfterRasterization", false)
.Add("cornerRadius", cornerRadius)
.Add("borderlineWidth", borderlineWidth)
.Add("borderlineColor", borderlineColor)
.Add("borderlineOffset", borderlineOffset)
.Add("desiredWidth", desiredWidth)
- .Add("desiredHeight", desiredHeight)
- .Add("useFixedCache", false);
+ .Add("desiredHeight", desiredHeight);
Visual::Base visual = VisualFactory::Get().CreateVisual(propertyMap);
DALI_TEST_CHECK(visual);
DALI_TEST_CHECK(value);
DALI_TEST_CHECK(value->Get<bool>() == false);
+ value = resultMap.Find(DevelImageVisual::Property::ENABLE_FRAME_CACHE, Property::BOOLEAN);
+ DALI_TEST_CHECK(value);
+ DALI_TEST_CHECK(value->Get<bool>() == false);
+
+ value = resultMap.Find(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION, Property::BOOLEAN);
+ DALI_TEST_CHECK(value);
+ DALI_TEST_CHECK(value->Get<bool>() == false);
+
value = resultMap.Find(DevelVisual::Property::CORNER_RADIUS, Property::VECTOR4);
DALI_TEST_CHECK(value);
DALI_TEST_EQUALS(value->Get<Vector4>(), Vector4(cornerRadius, cornerRadius, cornerRadius, cornerRadius), TEST_LOCATION);
DALI_TEST_CHECK(value);
DALI_TEST_CHECK(value->Get<bool>() == false); // Check default value
+ value = resultMap.Find(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION, Property::BOOLEAN);
+ DALI_TEST_CHECK(value);
+ DALI_TEST_CHECK(value->Get<bool>() == false); // Check default value
+
value = resultMap.Find(DevelImageVisual::Property::REDRAW_IN_SCALING_DOWN, Property::BOOLEAN);
DALI_TEST_CHECK(value);
DALI_TEST_CHECK(value->Get<bool>() == true); // Check default value
application.SendNotification();
application.Render();
- // Trigger count is 1 - render a frame
+ // Trigger count is 1 - load
DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
Vector2 controlSize(200.f, 200.f);
application.SendNotification();
application.Render();
- // Trigger count is 1 - load
+ // Trigger count is 1 - render a frame
DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
// renderer is added to actor
END_TEST;
}
+int UtcDaliAnimatedVectorImageVisualNotifyAfterRasterization(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline("UtcDaliAnimatedVectorImageVisualNotifyAfterRasterization");
+
+ Property::Map propertyMap;
+ propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
+ .Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME)
+ .Add(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION, true)
+ .Add(ImageVisual::Property::SYNCHRONOUS_LOADING, false);
+
+ Visual::Base visual = VisualFactory::Get().CreateVisual(propertyMap);
+ DALI_TEST_CHECK(visual);
+
+ DummyControl actor = DummyControl::New(true);
+ DummyControlImpl& dummyImpl = static_cast<DummyControlImpl&>(actor.GetImplementation());
+ dummyImpl.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual);
+
+ application.GetScene().Add(actor);
+
+ application.SendNotification();
+ application.Render();
+
+ // Trigger count is 1 - load
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+ Vector2 controlSize(200.f, 200.f);
+ actor.SetProperty(Actor::Property::SIZE, controlSize);
+
+ application.SendNotification();
+ application.Render();
+
+ // Trigger count is 1 - render a frame
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+ // Play animation
+ Property::Map attributes;
+ DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, attributes);
+
+ application.SendNotification();
+ application.Render();
+
+ // renderer is added to actor
+ DALI_TEST_CHECK(actor.GetRendererCount() == 1u);
+ Renderer renderer = actor.GetRendererAt(0u);
+ DALI_TEST_CHECK(renderer);
+
+ // Check renderer behavior
+ DALI_TEST_CHECK(renderer.GetProperty<int>(DevelRenderer::Property::RENDERING_BEHAVIOR) == DevelRenderer::Rendering::IF_REQUIRED);
+
+ Property::Map map = actor.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+ Property::Value* value = map.Find(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION);
+ DALI_TEST_CHECK(value->Get<bool>() == true);
+
+ propertyMap.Clear();
+ propertyMap.Add(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION, false);
+ DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, propertyMap);
+
+ application.SendNotification();
+ application.Render();
+
+ // Check renderer behavior again
+ DALI_TEST_CHECK(renderer.GetProperty<int>(DevelRenderer::Property::RENDERING_BEHAVIOR) == DevelRenderer::Rendering::CONTINUOUSLY);
+
+ map = actor.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+ value = map.Find(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION);
+ DALI_TEST_CHECK(value->Get<bool>() == false);
+
+ propertyMap.Clear();
+ propertyMap.Add(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION, true);
+ DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, propertyMap);
+
+ application.SendNotification();
+ application.Render();
+
+ // Check renderer behavior again
+ DALI_TEST_CHECK(renderer.GetProperty<int>(DevelRenderer::Property::RENDERING_BEHAVIOR) == DevelRenderer::Rendering::IF_REQUIRED);
+
+ map = actor.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+ value = map.Find(DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION);
+ DALI_TEST_CHECK(value->Get<bool>() == true);
+
+ END_TEST;
+}
+
int UtcDaliAnimatedVectorImageVisualAnimationFinishedSignal(void)
{
ToolkitTestApplication application;
#define DALI_TOOLKIT_DEVEL_API_VISUALS_IMAGE_VISUAL_PROPERTIES_DEVEL_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
/**
* @brief Whether to AnimatedVectorImageVisual fixed cache or not.
- * @details Name "EnableFrameCache", type Property::BOOLEAN.
+ * @details Name "enableFrameCache", type Property::BOOLEAN.
* If this property is true, AnimatedVectorImageVisual enable frame cache for loading and keeps loaded frame
* until the visual is removed. It reduces CPU cost when the animated image will be looping.
* But it can spend a lot of memory if the resource has high resolution image or many frame count.
- * @note It is used in the AnimatedImageVisual. The default is false
+ * @note It is used in the AnimatedVectorImageVisual. The default is false
*/
- ENABLE_FRAME_CACHE = ORIENTATION_CORRECTION + 16
+ ENABLE_FRAME_CACHE = ORIENTATION_CORRECTION + 16,
+
+ /**
+ * @brief Whether notify AnimatedVectorImageVisual to render thread after every rasterization or not.
+ * @details Name "notifyAfterRasterization", type Property::BOOLEAN.
+ * If this property is true, AnimatedVectorImageVisual send notify to render thread after every rasterization.
+ * If false, AnimatedVectorImageVisual set Renderer's Behaviour as Continouly (mean, always update the render thread.)
+ *
+ * This flag is useful if given resource has low fps, so we don't need to render every frame.
+ * @note It is used in the AnimatedVectorImageVisual. The default is false.
+ */
+ NOTIFY_AFTER_RASTERIZATION = ORIENTATION_CORRECTION + 17
};
} //namespace Property
#include <dali/devel-api/adaptor-framework/window-devel.h>
#include <dali/devel-api/common/stage.h>
#include <dali/devel-api/rendering/renderer-devel.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
#include <dali/integration-api/debug.h>
#include <dali/public-api/math/math-utils.h>
#include <dali/public-api/rendering/decorated-visual-renderer.h>
mCoreShutdown(false),
mRedrawInScalingDown(true),
mEnableFrameCache(false),
- mUseNativeImage(false)
+ mUseNativeImage(false),
+ mNotifyAfterRasterization(false)
{
// the rasterized image is with pre-multiplied alpha format
mImpl->mFlags |= Visual::Base::Impl::IS_PREMULTIPLIED_ALPHA;
map.Insert(Toolkit::ImageVisual::Property::DESIRED_WIDTH, mDesiredSize.GetWidth());
map.Insert(Toolkit::ImageVisual::Property::DESIRED_HEIGHT, mDesiredSize.GetHeight());
map.Insert(Toolkit::DevelImageVisual::Property::ENABLE_FRAME_CACHE, mEnableFrameCache);
+ map.Insert(Toolkit::DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION, mNotifyAfterRasterization);
}
void AnimatedVectorImageVisual::DoCreateInstancePropertyMap(Property::Map& map) const
{
DoSetProperty(Toolkit::DevelImageVisual::Property::ENABLE_FRAME_CACHE, keyValue.second);
}
+ else if(keyValue.first == NOTIFY_AFTER_RASTERIZATION)
+ {
+ DoSetProperty(Toolkit::DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION, keyValue.second);
+ }
}
}
}
break;
}
+
+ case Toolkit::DevelImageVisual::Property::NOTIFY_AFTER_RASTERIZATION:
+ {
+ bool notifyAfterRasterization = false;
+ if(value.Get(notifyAfterRasterization))
+ {
+ if(mNotifyAfterRasterization != notifyAfterRasterization)
+ {
+ mNotifyAfterRasterization = notifyAfterRasterization;
+
+ mAnimationData.notifyAfterRasterization = mNotifyAfterRasterization;
+ mAnimationData.resendFlag |= VectorAnimationTask::RESEND_NOTIFY_AFTER_RASTERIZATION;
+ }
+ }
+ break;
+ }
}
}
{
mVectorAnimationTask->ResourceReadySignal().Connect(this, &AnimatedVectorImageVisual::OnResourceReady);
mVectorAnimationTask->SetAnimationFinishedCallback(MakeCallback(this, &AnimatedVectorImageVisual::OnAnimationFinished));
- mVectorAnimationTask->SetForceRenderOnceCallback(MakeCallback(this, &AnimatedVectorImageVisual::OnForceRendering));
EncodedImageBuffer encodedImageBuffer;
}
}
- if(mImpl->mRenderer)
+ if(!mNotifyAfterRasterization && mImpl->mRenderer)
{
mImpl->mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::IF_REQUIRED);
}
}
-void AnimatedVectorImageVisual::OnForceRendering(uint32_t playStateId)
-{
- if(!mCoreShutdown)
- {
- Stage::GetCurrent().KeepRendering(0.0f); // Trigger event processing
- }
-}
-
void AnimatedVectorImageVisual::SendAnimationData()
{
if(DALI_UNLIKELY(mImpl == nullptr))
}
mVectorAnimationTask->SetAnimationData(mAnimationData);
- if(mImpl->mRenderer)
+ if(mImpl->mRenderer &&
+ ((mAnimationData.resendFlag & VectorAnimationTask::RESEND_PLAY_STATE) ||
+ (mAnimationData.resendFlag & VectorAnimationTask::RESEND_NOTIFY_AFTER_RASTERIZATION)))
{
- if(mAnimationData.playState == DevelImageVisual::PlayState::PLAYING)
+ if(!mNotifyAfterRasterization && mPlayState == DevelImageVisual::PlayState::PLAYING)
{
+ // Make rendering behaviour if we don't notify after rasterization, but animation playing.
mImpl->mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::CONTINUOUSLY);
}
else
{
+ // Otherwise, notify will be sended after rasterization. Make behaviour as required.
mImpl->mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::IF_REQUIRED);
}
}
*/
void OnAnimationFinished(uint32_t playStateId);
- /**
- * @brief Event callback from rasterize thread. This is called when we want to ensure rendering next frame.
- *
- * @param[in] argument Not using arguments
- */
- void OnForceRendering(uint32_t argument);
-
/**
* @brief Send animation data to the rasterize thread.
*/
bool mRedrawInScalingDown : 1;
bool mEnableFrameCache : 1;
bool mUseNativeImage : 1;
+ bool mNotifyAfterRasterization : 1;
};
} // namespace Internal
mLayerInfoCached(false),
mMarkerInfoCached(false),
mEnableFrameCache(false),
+ mNotifyAfterRasterization(false),
mSizeUpdated(false)
{
mVectorRenderer.UploadCompletedSignal().Connect(this, &VectorAnimationTask::OnUploadCompleted);
mVectorAnimationThread.RemoveEventTriggerCallbacks(mAnimationFinishedCallback.get());
mAnimationFinishedCallback.reset();
}
- if(mForceRenderOnceCallback)
- {
- mVectorAnimationThread.RemoveEventTriggerCallbacks(mForceRenderOnceCallback.get());
- mForceRenderOnceCallback.reset();
- }
if(mLoadCompletedCallback)
{
mVectorAnimationThread.RemoveEventTriggerCallbacks(mLoadCompletedCallback.get());
mAnimationFinishedCallback = std::unique_ptr<CallbackBase>(callback);
}
-void VectorAnimationTask::SetForceRenderOnceCallback(CallbackBase* callback)
-{
- Mutex::ScopedLock lock(mMutex);
- mForceRenderOnceCallback = std::unique_ptr<CallbackBase>(callback);
-}
-
void VectorAnimationTask::SetLoopCount(int32_t count)
{
if(mLoopCount != count)
}
// Forcely trigger render once if need.
- if(mNeedForceRenderOnceTrigger)
+ if(mNotifyAfterRasterization || mNeedForceRenderOnceTrigger)
{
Mutex::ScopedLock lock(mMutex);
- if(mForceRenderOnceCallback)
- {
- mVectorAnimationThread.AddEventTriggerCallback(mForceRenderOnceCallback.get(), mAppliedPlayStateId);
- }
+ mVectorAnimationThread.RequestForceRenderOnce();
mNeedForceRenderOnceTrigger = false;
}
SetCurrentFrameNumber(animationData.currentFrame);
}
+ if(animationData.resendFlag & VectorAnimationTask::RESEND_NOTIFY_AFTER_RASTERIZATION)
+ {
+ mNotifyAfterRasterization = animationData.notifyAfterRasterization;
+ }
+
if(animationData.resendFlag & VectorAnimationTask::RESEND_NEED_RESOURCE_READY)
{
mVectorRenderer.InvalidateBuffer();
*/
enum ResendFlags
{
- RESEND_PLAY_RANGE = 1 << 0,
- RESEND_LOOP_COUNT = 1 << 1,
- RESEND_STOP_BEHAVIOR = 1 << 2,
- RESEND_LOOPING_MODE = 1 << 3,
- RESEND_CURRENT_FRAME = 1 << 4,
- RESEND_SIZE = 1 << 5,
- RESEND_PLAY_STATE = 1 << 6,
- RESEND_NEED_RESOURCE_READY = 1 << 7,
- RESEND_DYNAMIC_PROPERTY = 1 << 8
+ RESEND_PLAY_RANGE = 1 << 0,
+ RESEND_LOOP_COUNT = 1 << 1,
+ RESEND_STOP_BEHAVIOR = 1 << 2,
+ RESEND_LOOPING_MODE = 1 << 3,
+ RESEND_CURRENT_FRAME = 1 << 4,
+ RESEND_SIZE = 1 << 5,
+ RESEND_PLAY_STATE = 1 << 6,
+ RESEND_NEED_RESOURCE_READY = 1 << 7,
+ RESEND_DYNAMIC_PROPERTY = 1 << 8,
+ RESEND_NOTIFY_AFTER_RASTERIZATION = 1 << 9,
};
/**
width(0),
height(0),
loopCount(-1),
- playStateId(0)
+ playStateId(0),
+ notifyAfterRasterization(false)
{
}
AnimationData& operator=(const AnimationData& rhs)
{
resendFlag |= rhs.resendFlag; // OR resend flag
- playRange = rhs.playRange;
- playState = rhs.playState;
- stopBehavior = rhs.stopBehavior;
- loopingMode = rhs.loopingMode;
- currentFrame = rhs.currentFrame;
- width = rhs.width;
- height = rhs.height;
- loopCount = rhs.loopCount;
- playStateId = rhs.playStateId;
+ playRange = rhs.playRange;
+ playState = rhs.playState;
+ stopBehavior = rhs.stopBehavior;
+ loopingMode = rhs.loopingMode;
+ currentFrame = rhs.currentFrame;
+ width = rhs.width;
+ height = rhs.height;
+ loopCount = rhs.loopCount;
+ playStateId = rhs.playStateId;
+ notifyAfterRasterization = rhs.notifyAfterRasterization;
dynamicProperties.insert(dynamicProperties.end(), rhs.dynamicProperties.begin(), rhs.dynamicProperties.end());
return *this;
}
uint32_t height;
int32_t loopCount;
uint32_t playStateId;
+ bool notifyAfterRasterization;
};
/**
*/
void SetAnimationFinishedCallback(CallbackBase* callback);
- /**
- * @brief This callback is called when we want to force render next frame.
- * @param[in] callback The force render once callback
- */
- void SetForceRenderOnceCallback(CallbackBase* callback);
-
/**
* @brief Gets the playing range in frame number.
* @param[out] startFrame The frame number to specify minimum progress.
*/
bool IsAnimating();
+ void KeepRasterizedBuffer(bool enableFrameCache)
+ {
+ mEnableFrameCache = enableFrameCache;
+ }
+
+ bool IsKeptRasterizedBuffer() const
+ {
+ return mEnableFrameCache;
+ }
+
public: // Implementation of AsyncTask
/**
* @copydoc Dali::AsyncTask::Process()
return "VectorAnimationTask";
}
- void KeepRasterizedBuffer(bool enableFrameCache)
- {
- mEnableFrameCache = enableFrameCache;
- }
-
- bool IsKeptRasterizedBuffer()
- {
- return mEnableFrameCache;
- }
-
private:
/**
* @brief Loads the animation file.
Mutex mMutex;
ResourceReadySignalType mResourceReadySignal;
std::unique_ptr<CallbackBase> mAnimationFinishedCallback{};
- std::unique_ptr<CallbackBase> mForceRenderOnceCallback{};
std::unique_ptr<CallbackBase> mLoadCompletedCallback{};
mutable Property::Map mCachedLayerInfo;
mutable Property::Map mCachedMarkerInfo;
mutable bool mLayerInfoCached : 1;
mutable bool mMarkerInfoCached : 1;
bool mEnableFrameCache : 1;
+ bool mNotifyAfterRasterization : 1;
bool mSizeUpdated : 1;
};
mSleepThread(MakeCallback(this, &VectorAnimationThread::OnAwakeFromSleep)),
mConditionalWait(),
mEventTriggerMutex(),
+ mAnimationTasksMutex(),
+ mTaskCompletedMutex(),
+ mLogFactory(Dali::Adaptor::Get().GetLogFactory()),
+ mTraceFactory(Dali::Adaptor::Get().GetTraceFactory()),
mNeedToSleep(false),
mDestroyThread(false),
- mLogFactory(Dali::Adaptor::Get().GetLogFactory()),
- mTraceFactory(Dali::Adaptor::Get().GetTraceFactory())
+ mEventTriggered(false),
+ mForceRenderOnce(false)
{
mAsyncTaskManager = Dali::AsyncTaskManager::Get();
mSleepThread.Start();
mEventTrigger = std::unique_ptr<EventThreadCallback>(new EventThreadCallback(MakeCallback(this, &VectorAnimationThread::OnEventCallbackTriggered)));
}
+/// Event thread called
VectorAnimationThread::~VectorAnimationThread()
{
// Stop the thread
ConditionalWait::ScopedLock lock(mConditionalWait);
// Wait until some event thread trigger relative job finished.
{
- Mutex::ScopedLock lock(mEventTriggerMutex);
+ Mutex::ScopedLock eventTriggerLock(mEventTriggerMutex);
+ Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
+ Mutex::ScopedLock animationTasksLock(mAnimationTasksMutex);
+ DALI_LOG_DEBUG_INFO("Mark VectorAnimationThread destroyed\n");
mDestroyThread = true;
}
mNeedToSleep = false;
DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "VectorAnimationThread::~VectorAnimationThread: Join [%p]\n", this);
+ DALI_LOG_DEBUG_INFO("VectorAnimationThread Join request\n");
+
Join();
}
+/// Event thread called
void VectorAnimationThread::AddTask(VectorAnimationTaskPtr task)
{
ConditionalWait::ScopedLock lock(mConditionalWait);
- // Find if the task is already in the list except loading task
- auto iter = std::find_if(mAnimationTasks.begin(), mAnimationTasks.end(), [task](VectorAnimationTaskPtr& element) { return (element == task && !element->IsLoadRequested()); });
- if(iter == mAnimationTasks.end())
+ // Rasterize as soon as possible
+ if(MoveTasksToAnimation(task, true))
{
- auto currentTime = task->CalculateNextFrameTime(true); // Rasterize as soon as possible
-
- bool inserted = false;
- for(auto iter = mAnimationTasks.begin(); iter != mAnimationTasks.end(); ++iter)
- {
- auto nextTime = (*iter)->GetNextFrameTime();
- if(nextTime > currentTime)
- {
- mAnimationTasks.insert(iter, task);
- inserted = true;
- break;
- }
- }
-
- if(!inserted)
- {
- mAnimationTasks.push_back(task);
- }
-
mNeedToSleep = false;
// wake up the animation thread
mConditionalWait.Notify(lock);
}
}
+/// Worker thread called
void VectorAnimationThread::OnTaskCompleted(VectorAnimationTaskPtr task, bool success, bool keepAnimation)
{
- if(!mDestroyThread)
- {
- ConditionalWait::ScopedLock lock(mConditionalWait);
- bool needRasterize = false;
-
- auto workingTask = std::find(mWorkingTasks.begin(), mWorkingTasks.end(), task);
- if(workingTask != mWorkingTasks.end())
- {
- mWorkingTasks.erase(workingTask);
- }
+ ConditionalWait::ScopedLock lock(mConditionalWait);
- // Check pending task
- if(mAnimationTasks.end() != std::find(mAnimationTasks.begin(), mAnimationTasks.end(), task))
- {
- needRasterize = true;
- }
+ Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
- if(keepAnimation && success)
- {
- if(mCompletedTasks.end() == std::find(mCompletedTasks.begin(), mCompletedTasks.end(), task))
- {
- mCompletedTasks.push_back(task);
- needRasterize = true;
- }
- }
+ if(DALI_LIKELY(!mDestroyThread))
+ {
+ mCompletedTasksQueue.emplace_back(task, success && keepAnimation);
- if(needRasterize)
- {
- mNeedToSleep = false;
- // wake up the animation thread
- mConditionalWait.Notify(lock);
- }
+ // wake up the animation thread.
+ // Note that we should not make mNeedToSleep as false now.
+ mConditionalWait.Notify(lock);
}
}
+/// VectorAnimationThread::SleepThread called
void VectorAnimationThread::OnAwakeFromSleep()
{
- if(!mDestroyThread)
+ if(DALI_LIKELY(!mDestroyThread))
{
mNeedToSleep = false;
// wake up the animation thread
}
}
+/// Worker thread called
void VectorAnimationThread::AddEventTriggerCallback(CallbackBase* callback, uint32_t argument)
{
Mutex::ScopedLock lock(mEventTriggerMutex);
- if(!mDestroyThread)
+ if(DALI_LIKELY(!mDestroyThread))
{
mTriggerEventCallbacks.emplace_back(callback, argument);
}
}
+/// Event thread called
void VectorAnimationThread::RemoveEventTriggerCallbacks(CallbackBase* callback)
{
Mutex::ScopedLock lock(mEventTriggerMutex);
- if(!mDestroyThread)
+ if(DALI_LIKELY(!mDestroyThread))
{
auto iter = std::remove_if(mTriggerEventCallbacks.begin(), mTriggerEventCallbacks.end(), [&callback](std::pair<CallbackBase*, uint32_t>& item) { return item.first == callback; });
mTriggerEventCallbacks.erase(iter, mTriggerEventCallbacks.end());
}
}
+/// Worker thread called
+void VectorAnimationThread::RequestForceRenderOnce()
+{
+ Mutex::ScopedLock lock(mEventTriggerMutex);
+ if(DALI_LIKELY(!mDestroyThread))
+ {
+ mForceRenderOnce = true;
+
+ if(!mEventTriggered)
+ {
+ mEventTrigger->Trigger();
+ mEventTriggered = true;
+ }
+ }
+}
+
+/// VectorAnimationThread called
void VectorAnimationThread::Run()
{
SetThreadName("VectorAnimationThread");
}
}
-void VectorAnimationThread::Rasterize()
+/// Event & VectorAnimationThread called
+bool VectorAnimationThread::MoveTasksToAnimation(VectorAnimationTaskPtr task, bool useCurrentTime)
{
- // Lock while popping task out from the queue
- ConditionalWait::ScopedLock lock(mConditionalWait);
-
- // conditional wait
- if(mNeedToSleep)
- {
- mConditionalWait.Wait(lock);
- }
-
- mNeedToSleep = true;
+ Mutex::ScopedLock animationTasksLock(mAnimationTasksMutex);
- // Process completed tasks
- for(auto&& task : mCompletedTasks)
+ if(DALI_LIKELY(!mDestroyThread))
{
- if(mAnimationTasks.end() == std::find(mAnimationTasks.begin(), mAnimationTasks.end(), task))
+ // Find if the task is already in the list except loading task
+ auto iter = std::find_if(mAnimationTasks.begin(), mAnimationTasks.end(), [task, useCurrentTime](VectorAnimationTaskPtr& element) {
+ return (element == task) &&
+ (!useCurrentTime || // If we don't need to use current time (i.e. CompletedTasks)
+ !element->IsLoadRequested()); // Or we need to use current time And loading completed.
+ });
+
+ if(iter == mAnimationTasks.end())
{
- // Should use the frame rate of the animation file
- auto nextFrameTime = task->CalculateNextFrameTime(false);
+ // Use the frame rate of the animation file, or use current time.
+ auto nextFrameTime = task->CalculateNextFrameTime(useCurrentTime);
bool inserted = false;
for(auto iter = mAnimationTasks.begin(); iter != mAnimationTasks.end(); ++iter)
{
- auto time = (*iter)->GetNextFrameTime();
- if(time > nextFrameTime)
+ auto nextTime = (*iter)->GetNextFrameTime();
+ if(nextTime > nextFrameTime)
{
mAnimationTasks.insert(iter, task);
inserted = true;
{
mAnimationTasks.push_back(task);
}
+
+ return true;
}
}
- mCompletedTasks.clear();
+ return false;
+}
- // pop out the next task from the queue
- for(auto it = mAnimationTasks.begin(); it != mAnimationTasks.end();)
+/// VectorAnimationThread called
+void VectorAnimationThread::MoveTasksToCompleted(VectorAnimationTaskPtr task, bool keepAnimation)
+{
+ if(DALI_LIKELY(!mDestroyThread))
{
- VectorAnimationTaskPtr nextTask = *it;
-
- auto currentTime = std::chrono::steady_clock::now();
- auto nextFrameTime = nextTask->GetNextFrameTime();
+ bool needRasterize = false;
-#if defined(DEBUG_ENABLED)
-// auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(nextFrameTime - currentTime);
+ auto workingTask = std::find(mWorkingTasks.begin(), mWorkingTasks.end(), task);
+ if(workingTask != mWorkingTasks.end())
+ {
+ mWorkingTasks.erase(workingTask);
+ }
-// DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "VectorAnimationThread::Rasterize: [next time = %lld]\n", duration.count());
-#endif
- if(nextFrameTime <= currentTime)
+ // Check pending task
{
- // If the task is not in the working list
- if(std::find(mWorkingTasks.begin(), mWorkingTasks.end(), nextTask) == mWorkingTasks.end())
+ Mutex::ScopedLock animationTasksLock(mAnimationTasksMutex);
+ if(mAnimationTasks.end() != std::find(mAnimationTasks.begin(), mAnimationTasks.end(), task))
{
- it = mAnimationTasks.erase(it);
+ needRasterize = true;
+ }
+ }
- // Add it to the working list
- mWorkingTasks.push_back(nextTask);
- mAsyncTaskManager.AddTask(nextTask);
+ if(keepAnimation)
+ {
+ if(mCompletedTasks.end() == std::find(mCompletedTasks.begin(), mCompletedTasks.end(), task))
+ {
+ mCompletedTasks.push_back(task);
+ needRasterize = true;
}
- else
+ }
+
+ if(needRasterize)
+ {
+ mNeedToSleep = false;
+ }
+ }
+}
+
+/// VectorAnimationThread called
+void VectorAnimationThread::Rasterize()
+{
+ // Lock while popping task out from the queue
+ ConditionalWait::ScopedLock lock(mConditionalWait);
+
+ // conditional wait
+ while(DALI_LIKELY(!mDestroyThread) && mNeedToSleep)
+ {
+ // ConditionalWait notifyed when sleep done, or task complete.
+ // If task complete, then we need to re-validate the tasks container, and then sleep again.
+ decltype(mCompletedTasksQueue) completedTasksQueue;
+ {
+ Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
+ completedTasksQueue.swap(mCompletedTasksQueue);
+ }
+ if(completedTasksQueue.empty())
+ {
+ mConditionalWait.Wait(lock);
+ // Task completed may have been added to the queue while we were waiting.
{
- it++;
+ Mutex::ScopedLock taskCompletedLock(mTaskCompletedMutex);
+ completedTasksQueue.swap(mCompletedTasksQueue);
}
}
- else
+
+ for(auto&& taskPair : completedTasksQueue)
{
- mSleepThread.SleepUntil(nextFrameTime);
- break;
+ auto& task = taskPair.first;
+ bool keepAnimation = taskPair.second;
+ MoveTasksToCompleted(task, keepAnimation);
+ }
+ }
+
+ mNeedToSleep = true;
+
+ // Process completed tasks
+ for(auto&& task : mCompletedTasks)
+ {
+ // Should use the frame rate of the animation file
+ MoveTasksToAnimation(task, false);
+ }
+ mCompletedTasks.clear();
+
+ {
+ Mutex::ScopedLock animationTasksLock(mAnimationTasksMutex);
+ if(DALI_LIKELY(!mDestroyThread))
+ {
+ if(mAnimationTasks.size() > 0u)
+ {
+ // pop out the next task from the queue
+ for(auto it = mAnimationTasks.begin(); it != mAnimationTasks.end();)
+ {
+ VectorAnimationTaskPtr nextTask = *it;
+
+ auto currentTime = std::chrono::steady_clock::now();
+ auto nextFrameTime = nextTask->GetNextFrameTime();
+
+ if(nextFrameTime <= currentTime)
+ {
+ // If the task is not in the working list
+ if(std::find(mWorkingTasks.begin(), mWorkingTasks.end(), nextTask) == mWorkingTasks.end())
+ {
+ it = mAnimationTasks.erase(it);
+
+ // Add it to the working list
+ mWorkingTasks.push_back(nextTask);
+ mAsyncTaskManager.AddTask(nextTask);
+ }
+ else
+ {
+ it++;
+ }
+ }
+ else
+ {
+ mSleepThread.SleepUntil(nextFrameTime);
+ break;
+ }
+ }
+ }
}
}
}
+/// Event thread called (Due to mTrigger triggered)
void VectorAnimationThread::OnEventCallbackTriggered()
{
while(true)
}
CallbackBase::Execute(*callbackPair.first, callbackPair.second);
}
+ // Request update once if we need.
+ {
+ Mutex::ScopedLock lock(mEventTriggerMutex);
+ if(!mDestroyThread && mForceRenderOnce)
+ {
+ mForceRenderOnce = false;
+ if(Dali::Adaptor::IsAvailable())
+ {
+ Dali::Adaptor::Get().UpdateOnce();
+ }
+ }
+ }
}
+/// Event thread called
std::pair<CallbackBase*, uint32_t> VectorAnimationThread::GetNextEventCallback()
{
Mutex::ScopedLock lock(mEventTriggerMutex);
{
ConditionalWait::ScopedLock lock(mConditionalWait);
mDestroyThread = true;
+ mAwakeCallback.reset();
mConditionalWait.Notify(lock);
}
Join();
}
+/// VectorAnimationThread called
void VectorAnimationThread::SleepThread::SleepUntil(std::chrono::time_point<std::chrono::steady_clock> timeToSleepUntil)
{
ConditionalWait::ScopedLock lock(mConditionalWait);
if(needToSleep)
{
-#if defined(DEBUG_ENABLED)
-// auto sleepDuration = std::chrono::duration_cast<std::chrono::milliseconds>(mSleepTimePoint - std::chrono::steady_clock::now());
-
-// DALI_LOG_INFO(gVectorAnimationLogFilter, Debug::Verbose, "VectorAnimationThread::SleepThread::Run: [sleep duration = %lld]\n", sleepDuration.count());
-#endif
-
std::this_thread::sleep_until(sleepTimePoint);
if(mAwakeCallback)
*/
void RemoveEventTriggerCallbacks(CallbackBase* callback);
+ /**
+ * @brief Request to event callback from rasterize thread. This is called when we want to ensure rendering next frame.
+ */
+ void RequestForceRenderOnce();
+
protected:
/**
* @brief The entry function of the animation thread.
*/
void Run() override;
+private:
+ /**
+ * @brief Move given tasks to mAnimationTasks if required.
+ * @return True if task added. False if not.
+ */
+ bool MoveTasksToAnimation(VectorAnimationTaskPtr task, bool useCurrentTime);
+
+ /**
+ * @brief Move given tasks to mCompletedTasks if required.
+ */
+ void MoveTasksToCompleted(VectorAnimationTaskPtr task, bool keepAnimation);
+
private:
/**
* @brief Rasterizes the tasks.
std::chrono::time_point<std::chrono::steady_clock> mSleepTimePoint;
const Dali::LogFactoryInterface& mLogFactory;
const Dali::TraceFactoryInterface& mTraceFactory;
- bool mNeedToSleep;
- bool mDestroyThread;
+
+ bool mNeedToSleep : 1;
+ bool mDestroyThread : 1;
};
private:
VectorAnimationThread& operator=(const VectorAnimationThread& thread) = delete;
private:
- std::vector<VectorAnimationTaskPtr> mAnimationTasks;
- std::vector<VectorAnimationTaskPtr> mCompletedTasks;
- std::vector<VectorAnimationTaskPtr> mWorkingTasks;
+ std::vector<VectorAnimationTaskPtr> mAnimationTasks; ///< Animation processing tasks, ordered by next frame time.
+ std::vector<VectorAnimationTaskPtr> mCompletedTasks; ///< Temperal storage for completed tasks.
+ std::vector<VectorAnimationTaskPtr> mWorkingTasks;
+
+ std::vector<std::pair<VectorAnimationTaskPtr, bool>> mCompletedTasksQueue; ///< Queue of completed tasks from worker thread. pair of task, and rasterize required.
+ ///< It will be moved at begin of Rasterize().
+
std::vector<std::pair<CallbackBase*, uint32_t>> mTriggerEventCallbacks{}; // Callbacks are not owned
SleepThread mSleepThread;
ConditionalWait mConditionalWait;
Mutex mEventTriggerMutex;
+ Mutex mAnimationTasksMutex; ///< Mutex to change + get mAnimationTasks from event thread
+ Mutex mTaskCompletedMutex; ///< Mutex to collect completed tasks to mCompletedTasksQueue from worker threads
std::unique_ptr<EventThreadCallback> mEventTrigger{};
- bool mNeedToSleep;
- bool mDestroyThread;
- bool mEventTriggered{false};
const Dali::LogFactoryInterface& mLogFactory;
const Dali::TraceFactoryInterface& mTraceFactory;
Dali::AsyncTaskManager mAsyncTaskManager;
+
+ bool mNeedToSleep : 1;
+ bool mDestroyThread : 1;
+ bool mEventTriggered : 1;
+ bool mForceRenderOnce : 1;
};
} // namespace Internal
const char* const FAST_TRACK_UPLOADING_NAME("fastTrackUploading");
const char* const ENABLE_BROKEN_IMAGE("enableBrokenImage");
const char* const ENABLE_FRAME_CACHE("enableFrameCache");
+const char* const NOTIFY_AFTER_RASTERIZATION("notifyAfterRasterization");
// Text visual
const char* const TEXT_PROPERTY("text");
extern const char* const FAST_TRACK_UPLOADING_NAME;
extern const char* const ENABLE_BROKEN_IMAGE;
extern const char* const ENABLE_FRAME_CACHE;
+extern const char* const NOTIFY_AFTER_RASTERIZATION;
// Text visual
extern const char* const TEXT_PROPERTY;