[Tizen] Lock mutex when animation task check + completed task moving 91/315991/1
authorEunki, Hong <eunkiki.hong@samsung.com>
Fri, 26 Jul 2024 05:08:12 +0000 (14:08 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Mon, 12 Aug 2024 08:00:23 +0000 (17:00 +0900)
Since VectorAnimationThread::OnTaskCompleted() could be called
from various worker threads, we should not change the thread's member value.

To avoid this kind of thread issue,
 - Make mutex during animation tasks control (VectorAnimationThread vs EventThread)
 - Collect tasks from worker thread and move them at Rasterize() API (VectorAnimationThread vs WorkerThread)

Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
(Vector) Add NOTIFY_AFTER_RASTERIZATION property for low fps file + Use UpdateOnce instead of KeepRendering

Let we change renderer rendering behavior is IF_REQUIRED.

It will be useful if app try to render lottie file which has less than 60fps.

+

Let we use UpdateOnce() API for force-rendering instead of KeepRendering().

KeepRendering will make ProcessCoreEvents forcely, which might not be need for animated vector cases.

Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
Reduce the number of UpdateOnce call after rasterization

Previous code request UpdateOnce by each visuals.
But this don't need to spend the number of requests.
So let we just use only one single request API for it.

Change-Id: I634706dade46528248a8e94bd876a99d4ed9cdfb
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
12 files changed:
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor-impl.h
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-adaptor.cpp
automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp
dali-toolkit/devel-api/visuals/image-visual-properties-devel.h
dali-toolkit/internal/visuals/animated-vector-image/animated-vector-image-visual.cpp
dali-toolkit/internal/visuals/animated-vector-image/animated-vector-image-visual.h
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.h
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-thread.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-thread.h
dali-toolkit/internal/visuals/visual-string-constants.cpp
dali-toolkit/internal/visuals/visual-string-constants.h

index 49c344aca2cf376d5f8e4d4891df31364514180f..6a69f938b4bab9deb76475accb529fc36bbf6361 100644 (file)
@@ -71,6 +71,8 @@ public:
   void RemoveIdle(CallbackBase* callback);
   void RunIdles();
 
+  void RequestUpdateOnce();
+
   static Integration::Scene GetScene(Dali::Window window);
 
   Dali::RenderSurfaceInterface& GetSurface();
index 4d77310c2a66d81a99becb23e6f13a79b3ff0f21..40c73ae1a74d46275baf12363438b170904f67bf 100644 (file)
@@ -139,6 +139,19 @@ void Adaptor::RunIdles()
   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());
index 72a496637934e2672c58a951789f2a8af0315896..5cac24b611eaa11cb97e2ceb7a296cc1f6835796 100644 (file)
@@ -208,13 +208,14 @@ int UtcDaliVisualFactoryGetAnimatedVectorImageVisual04(void)
     .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);
@@ -270,6 +271,14 @@ int UtcDaliVisualFactoryGetAnimatedVectorImageVisual04(void)
   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);
@@ -395,6 +404,10 @@ int UtcDaliAnimatedVectorImageVisualGetPropertyMap01(void)
   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
@@ -1123,7 +1136,7 @@ int UtcDaliAnimatedVectorImageVisualUsedFixedCache(void)
   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);
@@ -1132,7 +1145,7 @@ int UtcDaliAnimatedVectorImageVisualUsedFixedCache(void)
   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
@@ -1194,6 +1207,91 @@ int UtcDaliAnimatedVectorImageVisualUsedFixedCacheFailed(void)
   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;
index a5e135c2f8c04d439a3f18d7bef9d22f0f509caa..90e868e4da82b6cf7fe1e8b106914ad9af409fc9 100644 (file)
@@ -2,7 +2,7 @@
 #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.
@@ -193,13 +193,24 @@ enum Type
 
   /**
    * @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
index ac87ef87e912e8146a7b6554e07b4f545696c5a2..d0d6afd5f7783c5893f203ef97d23d85f56c9bcc 100644 (file)
@@ -22,6 +22,7 @@
 #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>
@@ -103,7 +104,8 @@ AnimatedVectorImageVisual::AnimatedVectorImageVisual(VisualFactoryCache& factory
   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;
@@ -221,6 +223,7 @@ void AnimatedVectorImageVisual::DoCreatePropertyMap(Property::Map& map) const
   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
@@ -285,6 +288,10 @@ void AnimatedVectorImageVisual::DoSetProperties(const Property::Map& propertyMap
       {
         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);
+      }
     }
   }
 
@@ -404,6 +411,22 @@ void AnimatedVectorImageVisual::DoSetProperty(Property::Index index, const Prope
       }
       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;
+    }
   }
 }
 
@@ -411,7 +434,6 @@ void AnimatedVectorImageVisual::OnInitialize(void)
 {
   mVectorAnimationTask->ResourceReadySignal().Connect(this, &AnimatedVectorImageVisual::OnResourceReady);
   mVectorAnimationTask->SetAnimationFinishedCallback(MakeCallback(this, &AnimatedVectorImageVisual::OnAnimationFinished));
-  mVectorAnimationTask->SetForceRenderOnceCallback(MakeCallback(this, &AnimatedVectorImageVisual::OnForceRendering));
 
   EncodedImageBuffer encodedImageBuffer;
 
@@ -725,20 +747,12 @@ void AnimatedVectorImageVisual::OnAnimationFinished(uint32_t playStateId)
     }
   }
 
-  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))
@@ -758,14 +772,18 @@ void AnimatedVectorImageVisual::SendAnimationData()
     }
     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);
       }
     }
index 7e7b04d8e48bf37b89671eb44d62de1bdef96b74..a1e74809373d22290647977aaadee50152617aa7 100644 (file)
@@ -187,13 +187,6 @@ private:
    */
   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.
    */
@@ -267,6 +260,7 @@ private:
   bool mRedrawInScalingDown : 1;
   bool mEnableFrameCache : 1;
   bool mUseNativeImage : 1;
+  bool mNotifyAfterRasterization : 1;
 };
 
 } // namespace Internal
index 423d3fcb2cf12091d29d77ccbacc64fe45ee1b72..ac448c022638886aba975689b0f162254d5d649a 100644 (file)
@@ -108,6 +108,7 @@ VectorAnimationTask::VectorAnimationTask(VisualFactoryCache& factoryCache)
   mLayerInfoCached(false),
   mMarkerInfoCached(false),
   mEnableFrameCache(false),
+  mNotifyAfterRasterization(false),
   mSizeUpdated(false)
 {
   mVectorRenderer.UploadCompletedSignal().Connect(this, &VectorAnimationTask::OnUploadCompleted);
@@ -139,11 +140,6 @@ void VectorAnimationTask::Finalize()
       mVectorAnimationThread.RemoveEventTriggerCallbacks(mAnimationFinishedCallback.get());
       mAnimationFinishedCallback.reset();
     }
-    if(mForceRenderOnceCallback)
-    {
-      mVectorAnimationThread.RemoveEventTriggerCallbacks(mForceRenderOnceCallback.get());
-      mForceRenderOnceCallback.reset();
-    }
     if(mLoadCompletedCallback)
     {
       mVectorAnimationThread.RemoveEventTriggerCallbacks(mLoadCompletedCallback.get());
@@ -395,12 +391,6 @@ void VectorAnimationTask::SetAnimationFinishedCallback(CallbackBase* callback)
   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)
@@ -747,13 +737,10 @@ bool VectorAnimationTask::Rasterize()
   }
 
   // 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;
   }
 
@@ -891,6 +878,11 @@ void VectorAnimationTask::ApplyAnimationData()
       SetCurrentFrameNumber(animationData.currentFrame);
     }
 
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_NOTIFY_AFTER_RASTERIZATION)
+    {
+      mNotifyAfterRasterization = animationData.notifyAfterRasterization;
+    }
+
     if(animationData.resendFlag & VectorAnimationTask::RESEND_NEED_RESOURCE_READY)
     {
       mVectorRenderer.InvalidateBuffer();
index 46976a5b4b1db89936cdee3090472f546e0810b1..c741be1c7d83b58de9767052ea4b7b6f05de94f1 100644 (file)
@@ -67,15 +67,16 @@ public:
    */
   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,
   };
 
   /**
@@ -94,22 +95,24 @@ public:
       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;
     }
@@ -125,6 +128,7 @@ public:
     uint32_t                             height;
     int32_t                              loopCount;
     uint32_t                             playStateId;
+    bool                                 notifyAfterRasterization;
   };
 
   /**
@@ -178,12 +182,6 @@ public:
    */
   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.
@@ -263,6 +261,16 @@ public:
    */
   bool IsAnimating();
 
+  void KeepRasterizedBuffer(bool enableFrameCache)
+  {
+    mEnableFrameCache = enableFrameCache;
+  }
+
+  bool IsKeptRasterizedBuffer() const
+  {
+    return mEnableFrameCache;
+  }
+
 public: // Implementation of AsyncTask
   /**
    * @copydoc Dali::AsyncTask::Process()
@@ -282,16 +290,6 @@ public: // Implementation of AsyncTask
     return "VectorAnimationTask";
   }
 
-  void KeepRasterizedBuffer(bool enableFrameCache)
-  {
-    mEnableFrameCache = enableFrameCache;
-  }
-
-  bool IsKeptRasterizedBuffer()
-  {
-    return mEnableFrameCache;
-  }
-
 private:
   /**
    * @brief Loads the animation file.
@@ -399,7 +397,6 @@ private:
   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;
@@ -433,6 +430,7 @@ private:
   mutable bool                         mLayerInfoCached : 1;
   mutable bool                         mMarkerInfoCached : 1;
   bool                                 mEnableFrameCache : 1;
+  bool                                 mNotifyAfterRasterization : 1;
   bool                                 mSizeUpdated : 1;
 };
 
index b07ba5b29a6e6db95c80ecef674fa4aa3221e584..8a4ce55f58fc13fd99b343fbc80c663f15a39020 100644 (file)
@@ -46,10 +46,14 @@ VectorAnimationThread::VectorAnimationThread()
   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();
@@ -57,6 +61,7 @@ VectorAnimationThread::VectorAnimationThread()
   mEventTrigger = std::unique_ptr<EventThreadCallback>(new EventThreadCallback(MakeCallback(this, &VectorAnimationThread::OnEventCallbackTriggered)));
 }
 
+/// Event thread called
 VectorAnimationThread::~VectorAnimationThread()
 {
   // Stop the thread
@@ -64,7 +69,10 @@ VectorAnimationThread::~VectorAnimationThread()
     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;
@@ -76,82 +84,46 @@ VectorAnimationThread::~VectorAnimationThread()
 
   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
@@ -159,10 +131,11 @@ void VectorAnimationThread::OnAwakeFromSleep()
   }
 }
 
+/// 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);
 
@@ -174,16 +147,34 @@ void VectorAnimationThread::AddEventTriggerCallback(CallbackBase* callback, uint
   }
 }
 
+/// 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");
@@ -196,32 +187,30 @@ void VectorAnimationThread::Run()
   }
 }
 
-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;
@@ -233,47 +222,137 @@ void VectorAnimationThread::Rasterize()
       {
         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)
@@ -285,8 +364,21 @@ void VectorAnimationThread::OnEventCallbackTriggered()
     }
     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);
@@ -321,12 +413,14 @@ VectorAnimationThread::SleepThread::~SleepThread()
   {
     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);
@@ -357,12 +451,6 @@ void VectorAnimationThread::SleepThread::Run()
 
     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)
index b1f608a87ef5cf548f9966f2d2a8736d5eafd3b6..ac5053b70d6f0b71a1209411e5358af815dd7aff 100644 (file)
@@ -90,12 +90,29 @@ public:
    */
   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.
@@ -149,8 +166,9 @@ private:
     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:
@@ -161,20 +179,28 @@ 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
index 6d89342b8af2f08e11b175c8db85dccb9da28160..ae39540d11f9e8777b5605d15d1f28997135ee03 100644 (file)
@@ -212,6 +212,7 @@ const char* const MASK_TEXTURE_RATIO_NAME("maskTextureRatio");
 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");
index cc97413d5ce41ce004fc0fd59a1c69891b41710d..9835299f5c11bed9d4895b0ff3650a6db85baf87 100644 (file)
@@ -114,6 +114,7 @@ extern const char* const MASK_TEXTURE_RATIO_NAME;
 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;