/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 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.
#include <toolkit-vector-animation-renderer.h>
#include <toolkit-event-thread-callback.h>
#include <memory>
+#include <thread>
+#include <chrono>
namespace Dali
{
namespace Adaptor
{
+namespace
+{
+Dali::Internal::Adaptor::VectorAnimationRenderer* gVectorAnimationRenderer = nullptr;
+}
+
class VectorAnimationRenderer: public Dali::BaseObject
{
public:
mRenderer(),
mWidth( 0 ),
mHeight( 0 ),
+ mTotalFrameNumber(VECTOR_ANIMATION_TOTAL_FRAME_NUMBER),
mPreviousFrame( 0 ),
+ mDelayTime(0),
+ mDroppedFrames(0),
mFrameRate( 60.0f ),
+ mNeedDroppedFrames(false),
mEventThreadCallback( new EventThreadCallback( MakeCallback( this, &VectorAnimationRenderer::OnTriggered ) ) )
{
mCount++;
{
return false;
}
+ else if(mUrl == "framedrop.json")
+ {
+ // Change total frame number for test
+ mTotalFrameNumber = 200;
+ }
return true;
}
bool Render( uint32_t frameNumber )
{
+ if(mDelayTime != 0)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int32_t>(mDelayTime)));
+ mDelayTime = 0;
+ mNeedDroppedFrames = true;
+ }
+ else if(mNeedDroppedFrames)
+ {
+ mDroppedFrames = (frameNumber > mPreviousFrame) ? frameNumber - mPreviousFrame - 1: frameNumber + (mTotalFrameNumber - mPreviousFrame) - 1;
+ mNeedTrigger = true;
+ mNeedDroppedFrames = false;
+ }
+
if( mNeedTrigger )
{
mEventThreadCallback->Trigger();
uint32_t GetTotalFrameNumber() const
{
- return VECTOR_ANIMATION_TOTAL_FRAME_NUMBER;
+ return mTotalFrameNumber;
}
float GetFrameRate() const
Dali::Renderer mRenderer;
uint32_t mWidth;
uint32_t mHeight;
+ uint32_t mTotalFrameNumber;
uint32_t mPreviousFrame;
+ uint32_t mDelayTime;
+ uint32_t mDroppedFrames;
float mFrameRate;
+ bool mNeedDroppedFrames;
Dali::VectorAnimationRenderer::UploadCompletedSignalType mUploadCompletedSignal;
std::unique_ptr< EventThreadCallback > mEventThreadCallback;
};
{
Internal::Adaptor::VectorAnimationRenderer* animationRenderer = new Internal::Adaptor::VectorAnimationRenderer();
+ Internal::Adaptor::gVectorAnimationRenderer = animationRenderer;
+
return VectorAnimationRenderer( animationRenderer );
}
Dali::Internal::Adaptor::VectorAnimationRenderer::mNeedTrigger = true;
}
+void DelayRendering(uint32_t delay)
+{
+ Dali::Internal::Adaptor::gVectorAnimationRenderer->mDelayTime = delay;
+}
+
+uint32_t GetDroppedFrames()
+{
+ return Dali::Internal::Adaptor::gVectorAnimationRenderer->mDroppedFrames;
+}
+
} // VectorAnimationRenderer
} // Test
#define DALI_TOOLKIT_TEST_VECTOR_ANIMATION_RENDERER_H
/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 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.
#define VECTOR_ANIMATION_MARKER_END_FRAME_2 3
void RequestTrigger();
+void DelayRendering(uint32_t delay);
+uint32_t GetDroppedFrames();
} // VectorAnimationRenderer
} // Test
/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2021 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.
{
const char* TEST_VECTOR_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/insta_camera.json";
+const char* TEST_VECTOR_IMAGE_FILE_NAME_FRAME_DROP = "framedrop.json";
const char* TEST_VECTOR_IMAGE_INVALID_FILE_NAME = "invalid.json";
bool gAnimationFinishedSignalFired = false;
END_TEST;
}
+
+int UtcDaliAnimatedVectorImageVisualFrameDrops(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline("UtcDaliAnimatedVectorImageVisualFrameDrops");
+
+ Property::Map propertyMap;
+ propertyMap.Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
+ .Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME_FRAME_DROP);
+
+ 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);
+
+ Vector2 controlSize(20.f, 30.f);
+ actor.SetProperty(Actor::Property::SIZE, controlSize);
+
+ application.GetScene().Add(actor);
+
+ application.SendNotification();
+ application.Render();
+
+ Property::Map attributes;
+ DevelControl::DoAction(actor, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PLAY, attributes);
+
+ application.SendNotification();
+ application.Render();
+
+ // Trigger count is 1 - render the first frame
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+ // Make delay to drop frames
+ Test::VectorAnimationRenderer::DelayRendering(170); // longer than 16.6 * 10frames
+
+ // Check dropped frame
+ DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+ uint32_t frames = Test::VectorAnimationRenderer::GetDroppedFrames();
+ DALI_TEST_CHECK(frames >= 9);
+
+ END_TEST;
+}
namespace
{
constexpr auto LOOP_FOREVER = -1;
-constexpr auto NANOSECONDS_PER_SECOND(1e+9);
+constexpr auto MICROSECONDS_PER_SECOND(1e+6);
#if defined(DEBUG_ENABLED)
Debug::Filter* gVectorAnimationLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_VECTOR_ANIMATION");
mStopBehavior(DevelImageVisual::StopBehavior::CURRENT_FRAME),
mLoopingMode(DevelImageVisual::LoopingMode::RESTART),
mNextFrameStartTime(),
- mFrameDurationNanoSeconds(0),
+ mFrameDurationMicroSeconds(MICROSECONDS_PER_SECOND / 60.0f),
mFrameRate(60.0f),
mCurrentFrame(0),
mTotalFrame(0),
mStartFrame(0),
mEndFrame(0),
+ mDroppedFrames(0),
mWidth(0),
mHeight(0),
mAnimationDataIndex(0),
mEndFrame = mTotalFrame - 1;
- mFrameRate = mVectorRenderer.GetFrameRate();
- mFrameDurationNanoSeconds = NANOSECONDS_PER_SECOND / mFrameRate;
+ mFrameRate = mVectorRenderer.GetFrameRate();
+ mFrameDurationMicroSeconds = MICROSECONDS_PER_SECOND / mFrameRate;
uint32_t width, height;
mVectorRenderer.GetDefaultSize(width, height);
bool VectorAnimationTask::Rasterize()
{
bool stopped = false;
- uint32_t currentFrame;
+ uint32_t currentFrame, droppedFrames = 0;
{
ConditionalWait::ScopedLock lock(mConditionalWait);
// The task will be destroyed. We don't need rasterization.
return false;
}
+ droppedFrames = mDroppedFrames;
}
ApplyAnimationData();
if(mPlayState == PlayState::PLAYING && mUpdateFrameNumber)
{
- mCurrentFrame = mForward ? mCurrentFrame + 1 : mCurrentFrame - 1;
+ mCurrentFrame = mForward ? mCurrentFrame + droppedFrames + 1 : mCurrentFrame - droppedFrames - 1;
Dali::ClampInPlace(mCurrentFrame, mStartFrame, mEndFrame);
}
return frame;
}
-std::chrono::time_point<std::chrono::system_clock> VectorAnimationTask::CalculateNextFrameTime(bool renderNow)
+VectorAnimationTask::TimePoint VectorAnimationTask::CalculateNextFrameTime(bool renderNow)
{
+ uint32_t droppedFrames = 0;
+
// std::chrono::time_point template has second parameter duration which defaults to the std::chrono::system_clock supported
// duration. In some C++11 implementations it is a milliseconds duration, so it fails to compile unless mNextFrameStartTime
// is casted to use the default duration.
- mNextFrameStartTime = std::chrono::time_point_cast<std::chrono::time_point<std::chrono::system_clock>::duration>(
- mNextFrameStartTime + std::chrono::nanoseconds(mFrameDurationNanoSeconds));
- auto current = std::chrono::system_clock::now();
- if(renderNow || mNextFrameStartTime < current)
+ mNextFrameStartTime = std::chrono::time_point_cast<TimePoint::duration>(mNextFrameStartTime + std::chrono::microseconds(mFrameDurationMicroSeconds));
+ auto current = std::chrono::system_clock::now();
+ if(renderNow)
+ {
+ mNextFrameStartTime = current;
+ }
+ else if(mNextFrameStartTime < current)
{
+ while(current > std::chrono::time_point_cast<TimePoint::duration>(mNextFrameStartTime + std::chrono::microseconds(mFrameDurationMicroSeconds)))
+ {
+ droppedFrames++;
+ mNextFrameStartTime = std::chrono::time_point_cast<TimePoint::duration>(mNextFrameStartTime + std::chrono::microseconds(mFrameDurationMicroSeconds));
+ }
+
+ {
+ ConditionalWait::ScopedLock lock(mConditionalWait);
+ mDroppedFrames = droppedFrames;
+ }
+
mNextFrameStartTime = current;
}
+
return mNextFrameStartTime;
}
-std::chrono::time_point<std::chrono::system_clock> VectorAnimationTask::GetNextFrameTime()
+VectorAnimationTask::TimePoint VectorAnimationTask::GetNextFrameTime()
{
return mNextFrameStartTime;
}
public:
using UploadCompletedSignalType = Dali::VectorAnimationRenderer::UploadCompletedSignalType;
+ using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
+
/**
* Flags for re-sending data to the vector animation thread
*/
* @brief Calculates the time for the next frame rasterization.
* @return The time for the next frame rasterization.
*/
- std::chrono::time_point<std::chrono::system_clock> CalculateNextFrameTime(bool renderNow);
+ TimePoint CalculateNextFrameTime(bool renderNow);
/**
* @brief Gets the time for the next frame rasterization.
* @return The time for the next frame rasterization.
*/
- std::chrono::time_point<std::chrono::system_clock> GetNextFrameTime();
+ TimePoint GetNextFrameTime();
private:
/**
PAUSED ///< The animation is paused
};
- std::string mUrl;
- VectorAnimationRenderer mVectorRenderer;
- AnimationData mAnimationData[2];
- VectorAnimationThread& mVectorAnimationThread;
- ConditionalWait mConditionalWait;
- std::unique_ptr<EventThreadCallback> mAnimationFinishedTrigger;
- PlayState mPlayState;
- DevelImageVisual::StopBehavior::Type mStopBehavior;
- DevelImageVisual::LoopingMode::Type mLoopingMode;
- std::chrono::time_point<std::chrono::system_clock> mNextFrameStartTime;
- int64_t mFrameDurationNanoSeconds;
- float mFrameRate;
- uint32_t mCurrentFrame;
- uint32_t mTotalFrame;
- uint32_t mStartFrame;
- uint32_t mEndFrame;
- uint32_t mWidth;
- uint32_t mHeight;
- uint32_t mAnimationDataIndex;
- int32_t mLoopCount;
- int32_t mCurrentLoop;
- bool mForward;
- bool mUpdateFrameNumber;
- bool mNeedAnimationFinishedTrigger;
- bool mAnimationDataUpdated;
- bool mDestroyTask;
+ std::string mUrl;
+ VectorAnimationRenderer mVectorRenderer;
+ AnimationData mAnimationData[2];
+ VectorAnimationThread& mVectorAnimationThread;
+ ConditionalWait mConditionalWait;
+ std::unique_ptr<EventThreadCallback> mAnimationFinishedTrigger;
+ PlayState mPlayState;
+ DevelImageVisual::StopBehavior::Type mStopBehavior;
+ DevelImageVisual::LoopingMode::Type mLoopingMode;
+ TimePoint mNextFrameStartTime;
+ int64_t mFrameDurationMicroSeconds;
+ float mFrameRate;
+ uint32_t mCurrentFrame;
+ uint32_t mTotalFrame;
+ uint32_t mStartFrame;
+ uint32_t mEndFrame;
+ uint32_t mDroppedFrames;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mAnimationDataIndex;
+ int32_t mLoopCount;
+ int32_t mCurrentLoop;
+ bool mForward;
+ bool mUpdateFrameNumber;
+ bool mNeedAnimationFinishedTrigger;
+ bool mAnimationDataUpdated;
+ bool mDestroyTask;
};
} // namespace Internal