/*
- * 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.
// CLASS HEADER
#include <dali/internal/update/manager/update-manager.h>
+// EXTERNAL INCLUDES
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+#include <dali/devel-api/common/map-wrapper.h>
+#include <dali/devel-api/common/set-wrapper.h>
+#else
+#include <unordered_map>
+#include <unordered_set>
+#endif
+
// INTERNAL INCLUDES
#include <dali/integration-api/core.h>
+#include <dali/integration-api/trace.h>
#include <dali/internal/common/owner-key-container.h>
Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_UPDATE_MANAGER");
} // unnamed namespace
#endif
+namespace
+{
+DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_UPDATE_PROCESS, false);
+} // namespace
using namespace Dali::Integration;
using Dali::Internal::Update::MessageQueue;
{
namespace
{
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+/**
+ * Flag whether property has changed, during the Update phase.
+ */
+enum ContainerRemovedFlagBits
+{
+ NOTHING = 0x00,
+ NODE = 0x01,
+ RENDERER = 0x02,
+ SHADER = 0x04,
+ TEXTURE_SET = 0x08,
+ ANIMATION = 0x10,
+ PROPERTY_NOTIFICATION = 0x20,
+ CUSTOM_OBJECT = 0x40,
+};
+
+/**
+ * @brief ContainerRemovedFlags alters behaviour of implementation
+ */
+using ContainerRemovedFlags = uint8_t;
+#endif
/**
* Helper to Erase an object from OwnerContainer using discard queue
* @param container to remove from
nodeDirtyFlags(NodePropertyFlags::TRANSFORM), // set to TransformFlag to ensure full update the first time through Update()
frameCounter(0),
renderingBehavior(DevelStage::Rendering::IF_REQUIRED),
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ containerRemovedFlags(ContainerRemovedFlagBits::NOTHING),
+#endif
+ discardAnimationFinishedAge(0u),
animationFinishedDuringUpdate(false),
previousUpdateScene(false),
renderTaskWaiting(false),
(*iter)->OnDestroy();
Node::Delete(*iter);
}
+ nodeIdMap.clear();
for(auto&& scene : scenes)
{
Vector<Node*> nodes; ///< A container of all instantiated nodes
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ using NodeIdMap = std::map<uint32_t, Node*>;
+
+ using PropertyBaseResetRequestedContainer = std::set<PropertyBase*>;
+#else
+ using NodeIdMap = std::unordered_map<uint32_t, Node*>;
+
+ using PropertyBaseResetRequestedContainer = std::unordered_set<PropertyBase*>;
+#endif
+ NodeIdMap nodeIdMap; ///< A container of nodes map by id.
+
Vector<Camera*> cameras; ///< A container of cameras. Note : these cameras are owned by Impl::nodes.
OwnerContainer<PropertyOwner*> customObjects; ///< A container of owned objects (with custom properties)
ResetterContainer<NodeResetter> nodeResetters; ///< A container of node resetters
ResetterContainer<RendererResetter> rendererResetters; ///< A container of renderer resetters
+ PropertyBaseResetRequestedContainer resetRequestedPropertyBases; ///< A container of property base to be resets
+
OwnerContainer<Animation*> animations; ///< A container of owned animations
OwnerContainer<PropertyNotification*> propertyNotifications; ///< A container of owner property notifications.
OwnerKeyContainer<Renderer> renderers; ///< A container of owned renderers
uint32_t frameCounter; ///< Frame counter used in debugging to choose which frame to debug and which to ignore.
DevelStage::Rendering renderingBehavior; ///< Set via DevelStage::SetRenderingBehavior
- bool animationFinishedDuringUpdate; ///< Flag whether any animations finished during the Update()
- bool previousUpdateScene; ///< True if the scene was updated in the previous frame (otherwise it was optimized out)
- bool renderTaskWaiting; ///< A REFRESH_ONCE render task is waiting to be rendered
- bool renderersAdded; ///< Flag to keep track when renderers have been added to avoid unnecessary processing
- bool renderingRequired; ///< True if required to render the current frame
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ ContainerRemovedFlags containerRemovedFlags; ///< cumulative container removed flags during current frame
+#endif
+
+ uint8_t discardAnimationFinishedAge : 2; ///< Age of EndAction::Discard animation Stop/Finished. It will make we call ResetToBaseValue at least 2 frames.
+
+ bool animationFinishedDuringUpdate : 1; ///< Flag whether any animations finished during the Update()
+ bool previousUpdateScene : 1; ///< True if the scene was updated in the previous frame (otherwise it was optimized out)
+ bool renderTaskWaiting : 1; ///< A REFRESH_ONCE render task is waiting to be rendered
+ bool renderersAdded : 1; ///< Flag to keep track when renderers have been added to avoid unnecessary processing
+ bool renderingRequired : 1; ///< True if required to render the current frame
private:
Impl(const Impl&); ///< Undefined
RenderTaskProcessor& renderTaskProcessor)
: mImpl(nullptr)
{
+ PropertyBase::RegisterResetterManager(*this);
+
mImpl = new Impl(notificationManager,
animationFinishedNotifier,
propertyNotifier,
UpdateManager::~UpdateManager()
{
delete mImpl;
+ PropertyBase::UnregisterResetterManager();
+
+ // Ensure to release memory pool
+ Animation::ResetMemoryPool();
+ Camera::ResetMemoryPool();
+ Node::ResetMemoryPool();
+ Renderer::ResetMemoryPool();
+ RenderItem::ResetMemoryPool();
+ RenderTaskList::ResetMemoryPool();
+ TextureSet::ResetMemoryPool();
}
void UpdateManager::InstallRoot(OwnerPointer<Layer>& layer)
rootLayer->AddInitializeResetter(*this);
+ // Do not allow to insert duplicated nodes.
+ // It could be happened if node id is overflowed.
+ DALI_ASSERT_ALWAYS(mImpl->nodeIdMap.insert({rootLayer->GetId(), rootLayer}).second);
+
mImpl->scenes.emplace_back(new Impl::SceneInfo(rootLayer));
}
}
}
+ mImpl->nodeIdMap.erase(layer->GetId());
+
mImpl->nodeDiscardQueue.Add(mSceneGraphBuffers.GetUpdateBufferIndex(), layer);
// Notify the layer about impending destruction
AddCamera(static_cast<Camera*>(rawNode));
}
+ // Do not allow to insert duplicated nodes.
+ // It could be happened if node id is overflowed.
+ DALI_ASSERT_ALWAYS(mImpl->nodeIdMap.insert({rawNode->GetId(), rawNode}).second);
+
mImpl->nodes.PushBack(rawNode);
+
rawNode->CreateTransform(&mImpl->transformManager);
}
if((*iter) == node)
{
mImpl->nodes.Erase(iter);
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mImpl->containerRemovedFlags |= ContainerRemovedFlagBits::NODE;
+#endif
break;
}
}
RemoveCamera(static_cast<Camera*>(node));
}
+ mImpl->nodeIdMap.erase(node->GetId());
+
mImpl->nodeDiscardQueue.Add(mSceneGraphBuffers.GetUpdateBufferIndex(), node);
// Notify the Node about impending destruction
void UpdateManager::RemoveObject(PropertyOwner* object)
{
mImpl->customObjects.EraseObject(object);
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mImpl->containerRemovedFlags |= ContainerRemovedFlagBits::CUSTOM_OBJECT;
+#endif
}
void UpdateManager::AddRenderTaskList(OwnerPointer<RenderTaskList>& taskList)
bool animationFinished = animation->Stop(mSceneGraphBuffers.GetUpdateBufferIndex());
- // Queue this animation into notify required animations. Since we need to send Finished signal
- mImpl->notifyRequiredAnimations.PushBack(animation->GetNotifyId());
-
mImpl->animationFinishedDuringUpdate = mImpl->animationFinishedDuringUpdate || animationFinished;
}
+void UpdateManager::ClearAnimation(Animation* animation)
+{
+ DALI_ASSERT_DEBUG(animation && "NULL animation called to clear");
+
+ animation->ClearAnimator(mSceneGraphBuffers.GetUpdateBufferIndex());
+}
+
void UpdateManager::RemoveAnimation(Animation* animation)
{
DALI_ASSERT_DEBUG(animation && "NULL animation called to remove");
mImpl->rendererResetters.PushBack(rendererResetter.Release());
}
+void UpdateManager::RequestPropertyBaseResetToBaseValue(PropertyBase* propertyBase)
+{
+ mImpl->resetRequestedPropertyBases.insert(propertyBase);
+}
+
void UpdateManager::AddPropertyNotification(OwnerPointer<PropertyNotification>& propertyNotification)
{
mImpl->propertyNotifications.PushBack(propertyNotification.Release());
if(iter != mImpl->propertyNotifications.End())
{
mImpl->propertyNotifications.Erase(iter);
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mImpl->containerRemovedFlags |= ContainerRemovedFlagBits::PROPERTY_NOTIFICATION;
+#endif
}
}
{
// Find the shader and destroy it
EraseUsingDiscardQueue(mImpl->shaders, shader, mImpl->shaderDiscardQueue, mSceneGraphBuffers.GetUpdateBufferIndex());
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mImpl->containerRemovedFlags |= ContainerRemovedFlagBits::SHADER;
+#endif
}
void UpdateManager::SaveBinary(Internal::ShaderDataPtr shaderData)
EraseUsingDiscardQueue(mImpl->renderers, rendererKey, mImpl->rendererDiscardQueue, mSceneGraphBuffers.GetUpdateBufferIndex());
// Need to remove the render object as well
rendererKey->DisconnectFromSceneGraph(*mImpl->sceneController, mSceneGraphBuffers.GetUpdateBufferIndex());
+
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mImpl->containerRemovedFlags |= ContainerRemovedFlagBits::RENDERER;
+#endif
}
void UpdateManager::AttachRenderer(Node* node, Renderer* renderer)
void UpdateManager::RemoveTextureSet(TextureSet* textureSet)
{
mImpl->textureSets.EraseObject(textureSet);
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mImpl->containerRemovedFlags |= ContainerRemovedFlagBits::TEXTURE_SET;
+#endif
}
uint32_t* UpdateManager::ReserveMessageSlot(uint32_t size, bool updateScene)
// Clear the "animations finished" flag; This should be set if any (previously playing) animation is stopped
mImpl->animationFinishedDuringUpdate = false;
+ // Age down discard animations.
+ mImpl->discardAnimationFinishedAge >>= 1;
+
+ // Ensure that their was no request to reset to base values during the previous update
+ // (Since requested property base doesn't consider the lifecycle of PropertyBase,
+ // It might be invalid after the previous update finished)
+ DALI_ASSERT_DEBUG(mImpl->resetRequestedPropertyBases.empty() && "Reset to base values requested during the previous update!");
+ mImpl->resetRequestedPropertyBases.clear();
+
// Reset node properties
- mImpl->nodeResetters.ResetToBaseValues(bufferIndex);
+ mImpl->nodeResetters.RequestResetToBaseValues();
// Reset renderer properties
- mImpl->rendererResetters.ResetToBaseValues(bufferIndex);
+ mImpl->rendererResetters.RequestResetToBaseValues();
// Reset all animating / constrained properties
- mImpl->propertyResetters.ResetToBaseValues(bufferIndex);
+ mImpl->propertyResetters.RequestResetToBaseValues();
+
+ // Actual reset to base values here
+ for(auto&& propertyBase : mImpl->resetRequestedPropertyBases)
+ {
+ propertyBase->ResetToBaseValue(bufferIndex);
+ }
+ mImpl->resetRequestedPropertyBases.clear();
// Clear all root nodes dirty flags
for(auto& scene : mImpl->scenes)
bool UpdateManager::Animate(BufferIndex bufferIndex, float elapsedSeconds)
{
bool animationActive = false;
- bool animationLooped = false;
+
+ if(mImpl->animations.Empty())
+ {
+ return animationActive;
+ }
auto&& iter = mImpl->animations.Begin();
+ DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_ANIMATION_ANIMATE", [&](std::ostringstream& oss) {
+ oss << "[" << mImpl->animations.Count() << "]";
+ });
+
while(iter != mImpl->animations.End())
{
Animation* animation = *iter;
bool finished = false;
- bool looped = false;
+ bool stopped = false;
bool progressMarkerReached = false;
- animation->Update(bufferIndex, elapsedSeconds, looped, finished, progressMarkerReached);
+ animation->Update(bufferIndex, elapsedSeconds, stopped, finished, progressMarkerReached);
animationActive = animationActive || animation->IsActive();
}
mImpl->animationFinishedDuringUpdate = mImpl->animationFinishedDuringUpdate || finished;
- animationLooped = animationLooped || looped;
- // queue the notification on finished or stoped or looped (to update loop count)
- if(finished || looped)
+ // Check whether finished animation is Discard type. If then, we should update scene at least 2 frames.
+ if(finished && animation->GetEndAction() == Animation::EndAction::DISCARD)
+ {
+ mImpl->discardAnimationFinishedAge |= 2u;
+ }
+
+ // queue the notification on finished or stopped
+ if(finished || stopped)
{
mImpl->notifyRequiredAnimations.PushBack(animation->GetNotifyId());
}
if(animation->GetState() == Animation::Destroyed)
{
iter = mImpl->animations.Erase(iter);
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mImpl->containerRemovedFlags |= ContainerRemovedFlagBits::ANIMATION;
+#endif
}
else
{
mImpl->notificationManager.QueueNotification(&mImpl->animationPlaylist, std::move(mImpl->notifyRequiredAnimations));
}
+ DALI_TRACE_END_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_ANIMATION_ANIMATE", [&](std::ostringstream& oss) {
+ oss << "[" << mImpl->animations.Count() << "]";
+ });
+
return animationActive;
}
void UpdateManager::UpdateRenderers(PropertyOwnerContainer& postPropertyOwners, BufferIndex bufferIndex)
{
+ if(mImpl->renderers.Empty())
+ {
+ return;
+ }
+
+ DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_UPDATE_RENDERERS", [&](std::ostringstream& oss) {
+ oss << "[" << mImpl->renderers.Count() << "]";
+ });
+
for(const auto& rendererKey : mImpl->renderers)
{
// Apply constraints
mImpl->renderingRequired = renderer->PrepareRender(bufferIndex) || mImpl->renderingRequired;
}
+
+ DALI_TRACE_END(gTraceFilter, "DALI_UPDATE_RENDERERS");
}
void UpdateManager::UpdateNodes(PropertyOwnerContainer& postPropertyOwners, BufferIndex bufferIndex)
isAnimationRunning || // ..at least one animation is running OR
mImpl->messageQueue.IsSceneUpdateRequired() || // ..a message that modifies the scene graph node tree is queued OR
mImpl->frameCallbackProcessor || // ..a frame callback processor is existed OR
+ mImpl->discardAnimationFinishedAge > 0u || // ..at least one animation with EndAction::DISCARD finished
gestureUpdated; // ..a gesture property was updated
- uint32_t keepUpdating = 0;
- bool keepRendererRendering = false;
- mImpl->renderingRequired = false;
+ uint32_t keepUpdating = 0;
+ mImpl->renderingRequired = false;
// Although the scene-graph may not require an update, we still need to synchronize double-buffered
// values if the scene was updated in the previous frame.
// We should not start skipping update steps or reusing lists until there has been two frames where nothing changes
if(updateScene || mImpl->previousUpdateScene)
{
+ DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_UPDATE_INTERNAL", [&](std::ostringstream& oss) {
+ oss << "[n:" << mImpl->nodes.Size() << ",";
+ oss << "c:" << mImpl->customObjects.Size() << ",";
+ oss << "a:" << mImpl->animations.Size() << ",";
+ oss << "r:" << mImpl->renderers.Size() << ",";
+ oss << "t:" << mImpl->textureSets.Size() << ",";
+ oss << "s:" << mImpl->shaders.Size() << "]";
+ });
+
// Animate
bool animationActive = Animate(bufferIndex, elapsedSeconds);
// Call the frame-callback-processor if set
if(mImpl->frameCallbackProcessor)
{
- keepRendererRendering |= mImpl->frameCallbackProcessor->Update(bufferIndex, elapsedSeconds);
+ if(mImpl->frameCallbackProcessor->Update(bufferIndex, elapsedSeconds))
+ {
+ keepUpdating |= KeepUpdating::FRAME_UPDATE_CALLBACK;
+ }
}
// Update node hierarchy, apply constraints,
if(mImpl->renderersAdded)
{
// Calculate how many render tasks we have in total
- std::size_t numberOfRenderTasks = 0;
+ std::size_t numberOfRenderTasks = 0;
+ std::size_t numberOfRenderInstructions = 0;
+ bool renderContinuously = false;
for(auto&& scene : mImpl->scenes)
{
if(scene && scene->taskList)
}
}
- std::size_t numberOfRenderInstructions = 0;
- mImpl->renderInstructionCapacity = 0u;
+ mImpl->renderInstructionCapacity = 0u;
for(auto&& scene : mImpl->scenes)
{
if(scene && scene->root && scene->taskList && scene->scene)
// or keep rendering is requested
if(!isAnimationRunning || animationActive || mImpl->renderingRequired || (mImpl->nodeDirtyFlags & RenderableUpdateFlags) || sceneKeepUpdating)
{
- keepRendererRendering |= mImpl->renderTaskProcessor.Process(bufferIndex,
- *scene->taskList,
- *scene->root,
- scene->sortedLayerList,
- scene->scene->GetRenderInstructions(),
- renderToFboEnabled,
- isRenderingToFbo);
+ renderContinuously |= mImpl->renderTaskProcessor.Process(bufferIndex,
+ *scene->taskList,
+ scene->sortedLayerList,
+ scene->scene->GetRenderInstructions(),
+ renderToFboEnabled,
+ isRenderingToFbo);
mImpl->renderInstructionCapacity += scene->scene->GetRenderInstructions().GetCapacity();
scene->scene->SetSkipRendering(false);
}
}
+ if(renderContinuously)
+ {
+ keepUpdating |= KeepUpdating::RENDERER_CONTINUOUSLY;
+ }
+
DALI_LOG_INFO(gLogFilter, Debug::General, "Update: numberOfRenderTasks(%d), Render Instructions(%d)\n", numberOfRenderTasks, numberOfRenderInstructions);
}
+
+ DALI_TRACE_END(gTraceFilter, "DALI_UPDATE_INTERNAL");
}
if(!uploadOnly)
// Macro is undefined in release build.
SNAPSHOT_NODE_LOGGING;
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ // Shrink relevant containers if required.
+ if(mImpl->containerRemovedFlags & ContainerRemovedFlagBits::NODE)
+ {
+ mImpl->nodes.ShrinkToFitIfNeeded();
+ }
+ if(mImpl->containerRemovedFlags & ContainerRemovedFlagBits::RENDERER)
+ {
+ mImpl->renderers.ShrinkToFitIfNeeded();
+ }
+ if(mImpl->containerRemovedFlags & ContainerRemovedFlagBits::SHADER)
+ {
+ mImpl->shaders.ShrinkToFitIfNeeded();
+ }
+ if(mImpl->containerRemovedFlags & ContainerRemovedFlagBits::TEXTURE_SET)
+ {
+ mImpl->textureSets.ShrinkToFitIfNeeded();
+ }
+ if(mImpl->containerRemovedFlags & ContainerRemovedFlagBits::ANIMATION)
+ {
+ mImpl->animations.ShrinkToFitIfNeeded();
+ }
+ if(mImpl->containerRemovedFlags & ContainerRemovedFlagBits::PROPERTY_NOTIFICATION)
+ {
+ mImpl->propertyNotifications.ShrinkToFitIfNeeded();
+ }
+ if(mImpl->containerRemovedFlags & ContainerRemovedFlagBits::CUSTOM_OBJECT)
+ {
+ mImpl->customObjects.ShrinkToFitIfNeeded();
+ }
+
+ // Reset flag
+ mImpl->containerRemovedFlags = ContainerRemovedFlagBits::NOTHING;
+#endif
+
// A ResetProperties() may be required in the next frame
mImpl->previousUpdateScene = updateScene;
// Check whether further updates are required
keepUpdating |= KeepUpdatingCheck(elapsedSeconds);
- if(keepRendererRendering)
- {
- keepUpdating |= KeepUpdating::STAGE_KEEP_RENDERING;
- }
-
- if(keepUpdating & KeepUpdating::STAGE_KEEP_RENDERING)
+ if(keepUpdating & (KeepUpdating::STAGE_KEEP_RENDERING | KeepUpdating::FRAME_UPDATE_CALLBACK | KeepUpdating::RENDERER_CONTINUOUSLY))
{
// Set dirty flags for next frame to continue rendering
mImpl->nodeDirtyFlags |= RenderableUpdateFlags;
// Reset dirty flag
for(auto&& renderer : mImpl->renderers)
{
- renderer->ResetDirtyFlag();
+ renderer->SetUpdated(false);
}
for(auto&& shader : mImpl->shaders)
// If the rendering behavior is set to continuously render, then continue to render.
// Keep updating until no messages are received and no animations are running.
- // If an animation has just finished, update at least once more for Discard end-actions.
+ // If an animation has just finished, update at least two frames more for Discard end-actions.
// No need to check for renderQueue as there is always a render after update and if that
// render needs another update it will tell the adaptor to call update again
}
if(IsAnimationRunning() ||
- mImpl->animationFinishedDuringUpdate)
+ mImpl->animationFinishedDuringUpdate ||
+ mImpl->discardAnimationFinishedAge > 0u)
{
keepUpdatingRequest |= KeepUpdating::ANIMATIONS_RUNNING;
}
mImpl->renderingRequired = true;
}
+Node* UpdateManager::GetNodePointerById(uint32_t nodeId) const
+{
+ Node* foundNodePointer = nullptr;
+ auto iter = mImpl->nodeIdMap.find(nodeId);
+ if(iter != mImpl->nodeIdMap.end())
+ {
+ foundNodePointer = iter->second;
+ }
+ return foundNodePointer;
+}
+
void UpdateManager::SetLayerDepths(const SortedLayerPointers& layers, const Layer* rootLayer)
{
for(auto&& scene : mImpl->scenes)