(ParticleSystem) Minor optimize and clean up codes (phase 1) 13/323613/9
authorEunki Hong <eunkiki.hong@samsung.com>
Wed, 30 Apr 2025 16:17:46 +0000 (01:17 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Thu, 8 May 2025 10:39:02 +0000 (19:39 +0900)
Several codes are not consider performances.
Let we make codes more clean and faster at UpdateRenderThread.

Also, remain some comments what we need to to in future.

Change-Id: I221d66fa3967b54d1d11b9c126c03c0c863f1ffd
Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
dali-toolkit/internal/particle-system/particle-emitter-impl.cpp
dali-toolkit/internal/particle-system/particle-emitter-impl.h
dali-toolkit/internal/particle-system/particle-impl.cpp
dali-toolkit/internal/particle-system/particle-list-impl.cpp
dali-toolkit/internal/particle-system/particle-list-impl.h
dali-toolkit/internal/particle-system/particle-renderer-impl.cpp
dali-toolkit/public-api/particle-system/particle-list.h

index 150d857b909694363b11aedca337d3b764f2b7a8..60a852689afb6457210d761782f56140d8bd9e0e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 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.
@@ -134,7 +134,10 @@ ParticleSystem::ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
 
 void ParticleEmitter::RemoveModifierAt(uint32_t index)
 {
-  mModifiers.erase(mModifiers.begin() + index);
+  if(DALI_LIKELY(index < mModifiers.size()))
+  {
+    mModifiers.erase(mModifiers.begin() + index);
+  }
 }
 
 void ParticleEmitter::Start()
@@ -148,7 +151,7 @@ void ParticleEmitter::Start()
 
     GetImplementation(mParticleRenderer).Initialize();
 
-    mSystemStarted = true;
+    mSystemStarted.store(1u, std::memory_order_relaxed);
     mParticleStatusBits &= ~(SIMULATION_STOPPED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
     mParticleStatusBits |= SIMULATION_STARTED_STATUS_BIT;
     mFrameCallback = std::make_unique<FrameCallback>(this);
@@ -164,7 +167,7 @@ void ParticleEmitter::Stop()
 {
   if(mActor && IsComplete() && (mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
   {
-    mSystemStarted = false;
+    mSystemStarted.store(0u, std::memory_order_relaxed);
     mParticleStatusBits &= ~(SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
     mParticleStatusBits |= SIMULATION_STOPPED_STATUS_BIT;
     auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
@@ -182,70 +185,67 @@ std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
 
 void ParticleEmitter::Update()
 {
-  // Do not update if emitter setup isn't complete
-  if(!IsComplete())
-  {
-    return;
-  }
-
-  auto ms = GetCurrentTimeMillis();
+  auto currentUpdateMs = GetCurrentTimeMillis();
 
   if(mCurrentMilliseconds.count() == 0)
   {
-    mCurrentMilliseconds = ms;
+    mCurrentMilliseconds = currentUpdateMs;
   }
 
   if(mLastUpdateMs.count() == 0)
   {
-    mLastUpdateMs = ms;
+    mLastUpdateMs = currentUpdateMs;
   }
 
+  // TODO : Make below codes as thread safe! See mSystemStarted implementation
+
   float emissionDelta = 1.0f / float(mEmissionRatePerSecond); // time per one particle emission (TODO: add some randomness to it)
 
-  auto diffTime = double((ms - mCurrentMilliseconds).count()) / 1000.0;
+  auto diffTime = double((currentUpdateMs - mCurrentMilliseconds).count()) / 1000.0;
 
   uint32_t emissionCount = 0u;
   if(diffTime >= emissionDelta)
   {
     emissionCount        = round(diffTime / emissionDelta);
-    mCurrentMilliseconds = ms;
+    mCurrentMilliseconds = mCurrentMilliseconds + std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double>(emissionCount * emissionDelta));
   }
 
   // Update lifetimes and discard dead particles
-  auto& particles = mParticleList.GetActiveParticles();
-  auto  dt        = ms - mLastUpdateMs;
-  if(dt.count())
+  const auto deltaMs = currentUpdateMs - mLastUpdateMs;
+
+  mLastUpdateMs = currentUpdateMs;
+
+  const float deltaSeconds = float(deltaMs.count()) / 1000.0f;
+  if(deltaMs.count())
   {
-    std::vector<int> toErase;
-    int              n = 0;
+    auto& particles = mParticleList.GetActiveParticles();
+
+    static std::vector<uint32_t> toEraseIndices;
+
+    uint32_t n = 0;
     for(auto& p : particles)
     {
       auto& lifetime = p.Get<float>(ParticleStream::LIFETIME_STREAM_BIT);
-      lifetime -= (float(dt.count()) / 1000.0f);
+      lifetime -= deltaSeconds;
       if(lifetime <= 0.0f)
       {
-        toErase.emplace_back(n);
+        toEraseIndices.emplace_back(n);
       }
       ++n;
     }
 
-    if(!toErase.empty())
+    if(!toEraseIndices.empty())
     {
-      int indexShift = 0;
-      for(auto& v : toErase)
-      {
-        GetImplementation(mParticleList).ReleaseParticle(v - indexShift);
-        ++indexShift;
-      }
+      GetImplementation(mParticleList).ReleaseParticles(toEraseIndices);
+      toEraseIndices.clear();
     }
   }
-  mLastUpdateMs = ms;
 
   // apply initial emission count
-  if(mSystemStarted)
+  const bool isFirstFrameAfterStart = mSystemStarted.fetch_and(0u, std::memory_order_relaxed);
+  if(isFirstFrameAfterStart)
   {
-    emissionCount  = mEmissionCountOnStart;
-    mSystemStarted = false;
+    emissionCount = mEmissionCountOnStart;
   }
 
   // Update source if there are any particles to be emitted
@@ -298,9 +298,11 @@ void ParticleEmitter::UpdateSource(uint32_t count)
 
 void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier)
 {
-  auto& threadPool    = GetThreadPool();
-  auto  workerThreads = threadPool.GetWorkerCount();
-  auto  activeCount   = mParticleList.GetActiveParticleCount();
+  auto& threadPool = GetThreadPool();
+
+  static const auto workerThreads = threadPool.GetWorkerCount();
+
+  const auto activeCount = mParticleList.GetActiveParticleCount();
 
   // at least 10 particles per worker thread (should be parametrized)
   // If less, continue ST
@@ -310,7 +312,7 @@ void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleMo
     return;
   }
 
-  auto partial = mParticleList.GetActiveParticleCount() / workerThreads;
+  const auto partial = activeCount / workerThreads;
 
   // make tasks
   struct UpdateTask
@@ -325,8 +327,8 @@ void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleMo
 
     Internal::ParticleModifier&   mModifier;
     ParticleSystem::ParticleList& mList;
-    uint32_t                      mFirst;
-    uint32_t                      mCount;
+    const uint32_t                mFirst;
+    const uint32_t                mCount;
 
     void Update()
     {
@@ -334,25 +336,28 @@ void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleMo
     }
   };
 
-  std::vector<UpdateTask> updateTasks;
+  static std::vector<UpdateTask> updateTasks;
   updateTasks.reserve(workerThreads);
-  std::vector<Task> tasks;
+
+  static std::vector<Task> tasks;
+  tasks.reserve(workerThreads);
 
   for(auto i = 0u; i < workerThreads; ++i)
   {
-    auto index = i * partial;
-    auto count = partial;
-    if(i == workerThreads - 1 && index + count < activeCount)
-    {
-      count = activeCount - index;
-    }
+    const auto index = i * partial;
+    const auto count = (i == workerThreads - 1) ? activeCount - index : partial;
 
     updateTasks.emplace_back(GetImplementation(modifier), mParticleList, index, count);
-    tasks.emplace_back([&task = updateTasks.back()](uint32_t n) { task.Update(); });
+    tasks.emplace_back([&task = updateTasks.back()](uint32_t n)
+                       { task.Update(); });
   }
 
   auto future = threadPool.SubmitTasks(tasks, 0);
   future->Wait();
+
+  // clear tasks
+  updateTasks.clear();
+  tasks.clear();
 }
 
 void ParticleEmitter::UpdateDomain()
@@ -449,7 +454,8 @@ Dali::ThreadPool& GetThreadPool()
   // NOTE: this function shouldn't be called from multiple thread anyway
   if(!gThreadPool)
   {
-    std::call_once(onceFlag, [&threadPool = gThreadPool] { threadPool = std::make_unique<Dali::ThreadPool>();
+    std::call_once(onceFlag, [&threadPool = gThreadPool]
+                   { threadPool = std::make_unique<Dali::ThreadPool>();
                      threadPool->Initialize(4u); });
   }
 
index 8b2459e1b1d3a4ec6d422a1f3610255b6189d57b..7e7bfc73647789a1a1a14039b133a9d305b04796 100644 (file)
@@ -61,6 +61,8 @@ public:
     return (mParticleStatusBits & STATUS_COMPLETE_BITS) == STATUS_COMPLETE_BITS;
   }
 
+  // TODO : Split below API per thread accessness.
+
   [[nodiscard]] ParticleSystem::ParticleSource GetSource() const;
 
   void SetSource(const ParticleSystem::ParticleSource& source);
@@ -119,6 +121,7 @@ public:
 
   [[nodiscard]] ParticleSystem::ParticleEmitter::Status GetStatus() const;
 
+  // DevNote : Need to be define for create fake time getter at UTC
   [[nodiscard]] std::chrono::milliseconds GetCurrentTimeMillis() const;
 
   // All these bits must be set in order to consider emitter COMPLETE
@@ -151,7 +154,7 @@ public:
   uint32_t                  mEmissionRatePerSecond{1u};
   std::atomic<uint32_t>     mEmissionCountOnStart{0u};
   std::atomic<uint32_t>     mActiveParticlesLimit{0u}; ///< 0 - unlimited
-  std::atomic<bool>         mSystemStarted{false};
+  std::atomic<uint8_t >     mSystemStarted{0u};        ///< 0 - not started, 1 - started
   std::chrono::milliseconds mCurrentMilliseconds{0};
   std::chrono::milliseconds mLastUpdateMs{0};
 
index 2a5ed3bf140df150dcf1e386e1df90af0eae60aa..476233011ac744bd39b14fc11cd24ceeb9f08e9d 100644 (file)
@@ -29,15 +29,15 @@ Particle::Particle(Internal::ParticleList& ownerList, uint32_t index)
 
 void* Particle::Get(ParticleStreamTypeFlagBit streamBit)
 {
-  auto streamIndex = mOwnerList.GetDefaultStreamIndex(streamBit);
-  auto dataSize    = mOwnerList.GetStreamDataTypeSize(streamIndex);
+  const auto streamIndex = mOwnerList.GetDefaultStreamIndex(streamBit);
+  const auto dataSize    = mOwnerList.GetStreamDataTypeSize(streamIndex);
   return reinterpret_cast<uint8_t*>(mOwnerList.GetDefaultStream(streamBit)) + (mIndex * dataSize);
 }
 
 void* Particle::GetByIndex(uint32_t streamIndex)
 {
-  auto  dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex);
-  auto* ptr      = reinterpret_cast<uint8_t*>(mOwnerList.GetRawStream(streamIndex));
+  const auto dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex);
+  auto*      ptr      = reinterpret_cast<uint8_t*>(mOwnerList.GetRawStream(streamIndex));
   return reinterpret_cast<uint8_t*>(ptr + (mIndex * dataSize));
 }
 
index 2115a365ea4e633849747b1e3f29430db0d98850..93bd6e58ffdf3ad1192a397a9d201d90e2611291 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 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.
@@ -55,45 +55,37 @@ ParticleList::ParticleList(uint32_t capacity, ParticleSystem::ParticleList::Part
   // initialize built-in streams and build map (to optimize later)
   if(streamFlags & ParticleStream::POSITION_STREAM_BIT)
   {
-    AddStream(Vector3::ZERO, "aStreamPosition", false);
-    mBuiltInStreamMap[uint32_t(ParticleStream::POSITION_STREAM_BIT)] = mDataStreams.size() - 1;
+    mBuiltInStreamMap[uint32_t(ParticleStream::POSITION_STREAM_BIT)] = AddStream(Vector3::ZERO, "aStreamPosition", false);
   }
   if(streamFlags & ParticleStream::ROTATION_STREAM_BIT)
   {
-    AddStream(Vector4::ZERO, "aStreamRotation", false);
-    mBuiltInStreamMap[uint32_t(ParticleStream::ROTATION_STREAM_BIT)] = mDataStreams.size() - 1;
+    mBuiltInStreamMap[uint32_t(ParticleStream::ROTATION_STREAM_BIT)] = AddStream(Vector4::ZERO, "aStreamRotation", false);
   }
   if(streamFlags & ParticleStream::SCALE_STREAM_BIT)
   {
-    AddStream(Vector3::ONE, "aStreamScale", false);
-    mBuiltInStreamMap[uint32_t(ParticleStream::SCALE_STREAM_BIT)] = mDataStreams.size() - 1;
+    mBuiltInStreamMap[uint32_t(ParticleStream::SCALE_STREAM_BIT)] = AddStream(Vector3::ONE, "aStreamScale", false);
   }
   if(streamFlags & ParticleStream::VELOCITY_STREAM_BIT)
   {
-    AddStream(Vector3::ZERO, "aStreamVelocity", false);
-    mBuiltInStreamMap[uint32_t(ParticleStream::VELOCITY_STREAM_BIT)] = mDataStreams.size() - 1;
+    mBuiltInStreamMap[uint32_t(ParticleStream::VELOCITY_STREAM_BIT)] = AddStream(Vector3::ZERO, "aStreamVelocity", false);
   }
   if(streamFlags & ParticleStream::COLOR_STREAM_BIT)
   {
-    AddStream(Color::YELLOW, "aStreamColor", false);
-    mBuiltInStreamMap[uint32_t(ParticleStream::COLOR_STREAM_BIT)] = mDataStreams.size() - 1;
+    mBuiltInStreamMap[uint32_t(ParticleStream::COLOR_STREAM_BIT)] = AddStream(Color::YELLOW, "aStreamColor", false);
   }
   if(streamFlags & ParticleStream::OPACITY_STREAM_BIT)
   {
-    AddStream(0.0f, "aStreamOpacity", false);
-    mBuiltInStreamMap[uint32_t(ParticleStream::OPACITY_STREAM_BIT)] = mDataStreams.size() - 1;
+    mBuiltInStreamMap[uint32_t(ParticleStream::OPACITY_STREAM_BIT)] = AddStream(0.0f, "aStreamOpacity", false);
   }
   if(streamFlags & ParticleStream::LIFETIME_STREAM_BIT)
   {
-    AddStream(0.0f, "aStreamLifetime", true);
-    mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_STREAM_BIT)] = mDataStreams.size() - 1;
-    AddStream(0.0f, "aStreamLifetimeBase", true);
-    mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_BASE_STREAM_BIT)] = mDataStreams.size() - 1;
+    mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_STREAM_BIT)]      = AddStream(0.0f, "aStreamLifetime", true);
+    mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_BASE_STREAM_BIT)] = AddStream(0.0f, "aStreamLifetimeBase", true);
   }
 
   // create free chain
   mFreeChain.resize(capacity);
-  for(auto i = 0u; i < mFreeChain.size(); ++i)
+  for(auto i = 0u; i + 1 < mFreeChain.size(); ++i)
   {
     mFreeChain[i] = i + 1;
   }
@@ -105,25 +97,14 @@ ParticleList::~ParticleList() = default;
 
 uint32_t ParticleList::AddStream(uint32_t sizeOfDataType, const void* defaultValue, ParticleStream::StreamDataType dataType, const char* streamName, bool localStream)
 {
-  mDataStreams.emplace_back(new ParticleDataStream(mMaxParticleCount, sizeOfDataType, defaultValue, dataType));
-  if(streamName)
-  {
-    mDataStreams.back()->SetStreamName(streamName);
-  }
-
-  mDataStreams.back()->SetStreamLocal(localStream);
+  mDataStreams.emplace_back(new ParticleDataStream(mMaxParticleCount, sizeOfDataType, defaultValue, dataType, streamName, localStream));
 
   // Update element size
-  mParticleStreamElementSize          = 0;
-  mParticleStreamElementSizeWithLocal = 0;
-  for(auto& ds : mDataStreams)
+  if(!localStream)
   {
-    if(!ds->localStream)
-    {
-      mParticleStreamElementSize += ds->dataSize;
-    }
-    mParticleStreamElementSizeWithLocal += ds->dataSize;
+    mParticleStreamElementSize += sizeOfDataType;
   }
+  mParticleStreamElementSizeWithLocal += sizeOfDataType;
 
   return mDataStreams.size() - 1;
 }
@@ -174,13 +155,14 @@ uint32_t ParticleList::GetStreamDataTypeSize(uint32_t streamIndex) const
 
 ParticleSystem::Particle ParticleList::NewParticle(float lifetime)
 {
-  if(mParticles.size() < mMaxParticleCount)
+  if(mAliveParticleCount < mMaxParticleCount)
   {
     auto newIndex = int32_t(mFreeIndex);
     mFreeIndex    = int32_t(mFreeChain[mFreeIndex]);
     mAliveParticleCount++;
 
     // Add particle
+    // TODO : Could we use a pool allocator here?
     mParticles.emplace_back(new Internal::Particle(*this, newIndex));
 
     // Set particle lifetime
@@ -208,22 +190,32 @@ uint32_t ParticleList::GetStreamElementSize(bool includeLocalStream)
   }
 }
 
-void ParticleList::ReleaseParticle(uint32_t particleIndex)
+void ParticleList::ReleaseParticles(const std::vector<uint32_t>& sortedEraseIndices)
 {
   auto it = mParticles.begin();
-  std::advance(it, particleIndex);
 
-  // Point at this slot of memory as next free slot
-  auto& p = *it;
-  if(mFreeIndex > -1)
+  mAliveParticleCount -= sortedEraseIndices.size();
+
+  uint32_t particleIndex = 0;
+  for(auto& index : sortedEraseIndices)
   {
+    // Find particle in the list
+    // TODO : Let we remove advancement in future.
+    while(particleIndex < index)
+    {
+      ++it;
+      ++particleIndex;
+    }
+
+    // Point at this slot of memory as next free slot
+    auto& p                  = *it;
     mFreeChain[p.GetIndex()] = mFreeIndex;
-  }
-  mFreeIndex = p.GetIndex();
+    mFreeIndex               = p.GetIndex();
 
-  // Remove particle from the list
-  mParticles.erase(it);
-  mAliveParticleCount--;
+    // Remove particle from the list
+    it = mParticles.erase(it);
+    ++particleIndex;
+  }
 }
 
 void* ParticleList::GetDefaultStream(ParticleStreamTypeFlagBit streamBit)
index 6f1ddeab99545ff05c670871904e148848e7aa21..4d1533fe0126cb0f141cf72d6fa46e77805020db 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_LIST_H
 #define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_LIST_H
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 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 <dali-toolkit/public-api/particle-system/particle.h>
 
 // EXTERNAL INCLUDES
-#include <dali/public-api/object/base-object.h>
-#include <dali/public-api/common/vector-wrapper.h>
-#include <dali/public-api/common/list-wrapper.h>
 #include <dali/devel-api/common/map-wrapper.h>
+#include <dali/public-api/common/list-wrapper.h>
+#include <dali/public-api/common/vector-wrapper.h>
+#include <dali/public-api/object/base-object.h>
 #include <algorithm>
+#include <cstring>
 #include <memory>
 
-
 namespace Dali::Toolkit::ParticleSystem::Internal
 {
 template<class T>
@@ -44,40 +44,27 @@ struct StreamDataTypeWrapper
 struct ParticleDataStream
 {
   ~ParticleDataStream() = default;
-  template<class T>
-  ParticleDataStream(uint32_t capacity, const T& defaultValue, ParticleStream::StreamDataType dataType)
-  : ParticleDataStream(capacity, sizeof(T), &defaultValue, dataType)
-  {
-  }
 
   /**
    * Creates new stream of requested capacity and (optionally) fills with default data
    */
-  ParticleDataStream(uint32_t capacity, uint32_t dataSize, const void* defaultValue, ParticleStream::StreamDataType dataType)
+  ParticleDataStream(uint32_t capacity, uint32_t dataSize, const void* defaultValue, ParticleStream::StreamDataType dataType, const char* name, bool localStream)
+  : type(dataType),
+    data(capacity * dataSize),
+    streamName(name ? name : ""),
+    dataSize(dataSize),
+    localStream(localStream)
   {
-    this->capacity = capacity;
-    data.resize(capacity * dataSize);
     if(defaultValue)
     {
+      auto*       dstPtr          = data.data();
+      const auto* defaultValuePtr = reinterpret_cast<const uint8_t*>(defaultValue);
       for(auto i = 0u; i < capacity; ++i)
       {
-        auto dstPtr = data.data() + (i * dataSize);
-        std::copy(reinterpret_cast<const uint8_t*>(defaultValue), reinterpret_cast<const uint8_t*>(defaultValue) + dataSize, dstPtr);
+        memcpy(dstPtr, defaultValuePtr, dataSize);
+        dstPtr += dataSize;
       }
     }
-    type           = dataType;
-    alive          = 0u;
-    this->dataSize = dataSize;
-  }
-
-  void SetStreamName(const char* name)
-  {
-    streamName = name;
-  }
-
-  void SetStreamLocal(bool local)
-  {
-    localStream = local;
   }
 
   /**
@@ -92,8 +79,6 @@ struct ParticleDataStream
   ParticleStream::StreamDataType type;
   std::vector<uint8_t>           data;
   std::string                    streamName;
-  uint32_t                       alive{0u};
-  uint32_t                       capacity;
   uint32_t                       dataSize;
   bool                           localStream{true};
 };
@@ -109,7 +94,6 @@ struct ParticleDataStream
 class ParticleList : public Dali::BaseObject
 {
 public:
-
   ParticleList(uint32_t capacity, ParticleSystem::ParticleList::ParticleStreamTypeFlags streamFlags);
 
   ~ParticleList();
@@ -168,7 +152,7 @@ public:
 
   std::list<ParticleSystem::Particle>& GetParticles();
 
-  void ReleaseParticle(uint32_t particleIndex);
+  void ReleaseParticles(const std::vector<uint32_t>& sortedEraseIndices);
 
   uint32_t GetStreamElementSize(bool includeLocalStream);
 
index 1176190981ebccc66bd4a142da4d084cdbe21e83..aad839f660210ac12eb88a5d3829b811a4ed0e1f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 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.
 
 namespace Dali::Toolkit::ParticleSystem::Internal
 {
+namespace
+{
+/**
+ * @brief The number of vertex elements per each particle is 6.
+ */
+static constexpr uint32_t NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE = 6u;
+} // namespace
+
 ParticleRenderer::ParticleRenderer()
 {
   mStreamBufferUpdateCallback = Dali::VertexBufferUpdateCallback::New(this, &ParticleRenderer::OnStreamBufferUpdate);
@@ -112,7 +120,7 @@ void ParticleRenderer::CreateShader()
       streamAtttributes.Add(key, ATTR_TYPES[dataTypeIndex]);
 
       // Add shader attribute line
-      ss << "INPUT mediump " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n";
+      ss << "INPUT highp " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n";
     }
   }
 
@@ -128,31 +136,33 @@ void ParticleRenderer::CreateShader()
    */
   std::string vertexShaderCode = streamAttributesStr + std::string(
                                                          "//@version 100\n\
-      INPUT mediump vec2 aPosition;\n\
-      INPUT mediump vec2 aTexCoords;\n\
+      precision highp float;\n\
+      INPUT highp vec2 aPosition;\n\
+      INPUT highp vec2 aTexCoords;\n\
       \n\
       UNIFORM_BLOCK VertBlock \n\
       {\n\
-      UNIFORM mediump mat4   uMvpMatrix;\n\
-      UNIFORM mediump vec3   uSize;\n\
-      UNIFORM lowp vec4      uColor;\n\
+      UNIFORM highp mat4 uMvpMatrix;\n\
+      UNIFORM highp vec3 uSize;\n\
+      UNIFORM lowp  vec4 uColor;\n\
       };\n\
-      OUTPUT mediump vec2 vTexCoord;\n\
+      OUTPUT highp   vec2 vTexCoord;\n\
       OUTPUT mediump vec4 vColor;\n\
       \n\
       void main()\n\
       {\n\
-        vec4 pos = vec4(aPosition, 0.0, 1.0) * vec4(aStreamScale, 1.0);\n\
+        vec4 pos      = vec4(aPosition, 0.0, 1.0) * vec4(aStreamScale, 1.0);\n\
         vec4 position =  pos + vec4(aStreamPosition, 0.0);\n\
         vTexCoord     = aTexCoords;\n\
-        vColor = uColor * aStreamColor;\n\
+        vColor        = uColor * aStreamColor;\n\
         gl_Position   = uMvpMatrix * position ;\n\
       }\n");
 
   std::string fragmentShaderCode =
     {"//@version 100\n\
-      INPUT mediump vec2       vTexCoord;\n\
-      INPUT mediump vec4       vColor;\n\
+      precision highp float;\n\
+      INPUT highp   vec2 vTexCoord;\n\
+      INPUT mediump vec4 vColor;\n\
       UNIFORM sampler2D sTexture;\n\
       \n\
       void main()\n\
@@ -186,10 +196,12 @@ void ParticleRenderer::CreateShader()
     Vertex2D a5{Vector2(0.0f, 1.0f) - C, Vector2(0.0f, 1.0f)};
   } QUAD;
 
+  static_assert(sizeof(Quad2D) == sizeof(Vertex2D) * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE, "Quad2D must be 6x Vertex2D");
+
   std::vector<Quad2D> quads;
   quads.resize(mEmitter->GetParticleList().GetCapacity());
   std::fill(quads.begin(), quads.end(), QUAD);
-  vertexBuffer0.SetData(quads.data(), 6u * quads.size());
+  vertexBuffer0.SetData(quads.data(), quads.size() * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE);
 
   // Second vertex buffer with stream data
   VertexBuffer vertexBuffer1 = VertexBuffer::New(streamAtttributes);
@@ -213,12 +225,12 @@ void ParticleRenderer::CreateShader()
   mStreamBuffer = vertexBuffer1;
 
   // Set some initial data for streambuffer to force initialization
-  std::vector<uint8_t> data;
+  Dali::Vector<uint8_t> data;
+
   // Resize using only-non local streams
-  auto elementSize = mEmitter->GetParticleList().GetParticleDataSize(false);
-  data.resize(elementSize *
-              mEmitter->GetParticleList().GetCapacity() * 6u);
-  mStreamBuffer.SetData(data.data(), mEmitter->GetParticleList().GetCapacity() * 6u); // needed to initialize
+  const auto elementSize = mEmitter->GetParticleList().GetParticleDataSize(false);
+  data.ResizeUninitialized(elementSize * mEmitter->GetParticleList().GetCapacity() * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE);
+  mStreamBuffer.SetData(data.Begin(), mEmitter->GetParticleList().GetCapacity() * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE); // needed to initialize
 
   // Sets up callback
   mStreamBuffer.SetVertexBufferUpdateCallback(std::move(mStreamBufferUpdateCallback));
@@ -258,33 +270,25 @@ void ParticleRenderer::CreateShader()
   }
 }
 
-uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
+uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t maxBytes)
 {
   auto& list = GetImplementation(mEmitter->GetParticleList());
 
-  auto particleCount    = list.GetActiveParticleCount(); // active particle count
-  auto particleMaxCount = list.GetParticleCount();
+  const auto particleCount = list.GetActiveParticleCount(); // active particle count
   if(!particleCount)
   {
     return 0;
   }
 
-  auto streamCount = list.GetStreamCount();
+  const auto particleMaxCount = list.GetParticleCount();
 
-  auto elementSize = 0u; // elements size should be cached (it's also stride of buffer) (in bytes)
-  for(auto i = 0u; i < streamCount; ++i)
-  {
-    if(!list.IsStreamLocal(i))
-    {
-      elementSize += list.GetStreamDataTypeSize(i);
-    }
-  }
+  const auto elementByte = list.GetStreamElementSize(false);
 
   // Prepare source buffer (MUST BE OPTIMIZED TO AVOID ALLOCATING AND COPYING!!)
-  auto totalSize = particleMaxCount * elementSize * 6u;
+  auto totalSize = particleMaxCount * elementByte * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE;
 
   // buffer sizes must match
-  if(totalSize != size)
+  if(DALI_UNLIKELY(totalSize != maxBytes))
   {
     // ASSERT here ?
     return 0;
@@ -292,10 +296,8 @@ uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
 
   auto* dst = reinterpret_cast<uint8_t*>(streamData);
 
-  auto& particles = list.GetParticles();
-
   // prepare worker threads
-  auto workerCount = GetThreadPool().GetWorkerCount();
+  static const auto workerCount = GetThreadPool().GetWorkerCount();
 
   // divide particles if over the threshold
 
@@ -307,7 +309,7 @@ uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
   else
   {
     // Partial to handle
-    auto partialSize = (particleCount / workerCount);
+    const auto partial = particleCount / workerCount;
 
     struct UpdateTask
     {
@@ -328,72 +330,43 @@ uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
 
       Internal::ParticleRenderer& owner;
       Internal::ParticleList&     particleList;
-      uint32_t                    startIndex;
-      uint32_t                    count;
+      const uint32_t              startIndex;
+      const uint32_t              count;
       uint8_t*                    ptr;
     };
 
-    std::vector<UpdateTask> tasks;
+    static std::vector<UpdateTask> tasks;
     tasks.reserve(workerCount);
-    std::vector<Task> taskQueue;
-    auto              count = partialSize;
+
+    static std::vector<Task> taskQueue;
+    taskQueue.reserve(workerCount);
 
     for(auto i = 0u; i < workerCount; ++i)
     {
-      auto index = i * partialSize;
-      count      = partialSize;
+      const auto index = i * partial;
+      const auto count = i == workerCount - 1 ? particleCount - index : partial;
 
-      // make sure there's no leftover particles!
-      if(i == workerCount - 1 && index + count < particleCount)
-      {
-        count = particleCount - index;
-      }
-
-      tasks.emplace_back(*this, list, index, count, streamData);
-      taskQueue.emplace_back([&t = tasks.back()](uint32_t threadId) { t.Update(); });
+      tasks.emplace_back(*this, list, index, count, dst + (elementByte * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE) * index);
+      taskQueue.emplace_back([&t = tasks.back()](uint32_t threadId)
+                             { t.Update(); });
     }
 
     // Execute worker tasks
     auto future = GetThreadPool().SubmitTasks(taskQueue, 0);
     // wait to finish
     future->Wait();
+
+    // clear tasks
+    tasks.clear();
+    taskQueue.clear();
   }
 
   // less particles so run on a single thread
   if(!runParallel)
   {
-    for(auto& p : particles)
-    {
-      // without instancing we need to duplicate data 4 times per each quad
-      auto* particleDst = dst;
-      for(auto s = 0u; s < streamCount; ++s)
-      {
-        if(!list.IsStreamLocal(s))
-        {
-          // Pointer to stream value
-          auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
-
-          // Size of data
-          auto dataSize = list.GetStreamDataTypeSize(s);
-
-          memcpy(dst, valuePtr, dataSize);
-          dst += dataSize;
-        }
-      }
-      // Replicate data 5 more times for each vertex (GLES2)
-      memcpy(dst, particleDst, elementSize);
-      dst += elementSize;
-      memcpy(dst, particleDst, elementSize);
-      dst += elementSize;
-      memcpy(dst, particleDst, elementSize);
-      dst += elementSize;
-      memcpy(dst, particleDst, elementSize);
-      dst += elementSize;
-      memcpy(dst, particleDst, elementSize);
-      dst += elementSize;
-    }
+    UpdateParticlesTask(list, 0, particleCount, dst);
   }
-  return particleCount * 6u; // return number of elements to render
+  return particleCount * elementByte * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE; // return byte of elements to render
 }
 
 Renderer ParticleRenderer::GetRenderer() const
@@ -404,15 +377,14 @@ Renderer ParticleRenderer::GetRenderer() const
 void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list,
                                            uint32_t                particleStartIndex,
                                            uint32_t                particleCount,
-                                           uint8_t*                basePtr)
+                                           uint8_t*                dst)
 {
-  auto& particles   = list.GetParticles();
-  auto  streamCount = list.GetStreamCount();
-  auto  elementSize = list.GetStreamElementSize(false);
+  const auto streamCount = list.GetStreamCount();
+  const auto elementByte = list.GetStreamElementSize(false);
 
-  // calculate begin of buffer
-  uint8_t* dst = (basePtr + (elementSize * 6u) * particleStartIndex);
+  auto& particles = list.GetParticles();
 
+  // TODO : Let we remove advancement in future.
   auto it = particles.begin();
   std::advance(it, particleStartIndex);
 
@@ -436,16 +408,11 @@ void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list,
       }
     }
     // Replicate data 5 more times for each vertex (GLES2)
-    memcpy(dst, particleDst, elementSize);
-    dst += elementSize;
-    memcpy(dst, particleDst, elementSize);
-    dst += elementSize;
-    memcpy(dst, particleDst, elementSize);
-    dst += elementSize;
-    memcpy(dst, particleDst, elementSize);
-    dst += elementSize;
-    memcpy(dst, particleDst, elementSize);
-    dst += elementSize;
+    for(auto vertexCopyCount = 0u; vertexCopyCount < NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE - 1; ++vertexCopyCount)
+    {
+      memcpy(dst, particleDst, elementByte);
+      dst += elementByte;
+    }
   }
 }
 
index 7fea6b645df0a4c065045b0fdd04b6051a89543c..ff4ad6f552caaed8b4ebc057443c3e0de0c0ef79 100644 (file)
@@ -228,6 +228,10 @@ public:
    */
   int GetDefaultStreamIndex(ParticleStreamTypeFlagBit defaultStreamBit);
 
+  /**
+   * @brief Returns raw data container of the particle list
+   * @return list of particles
+   */
   std::list<Particle>& GetActiveParticles();
 
 private:
@@ -241,8 +245,7 @@ private:
    * @param[in] localStream Flag indicating whether stream is local (not used in shaders) or not
    * @return Index of new stream
    */
-  uint32_t
-  AddStream(void* defaults, size_t dataTypeSize, ParticleStream::StreamDataType dataType, bool localStream);
+  uint32_t AddStream(void* defaults, size_t dataTypeSize, ParticleStream::StreamDataType dataType, bool localStream);
   /// @endcond
 
   /// @cond internal