2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
22 #include <dali-toolkit/internal/particle-system/particle-list-impl.h>
23 #include <dali-toolkit/internal/particle-system/particle-modifier-impl.h>
24 #include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
25 #include <dali-toolkit/internal/particle-system/particle-source-impl.h>
28 #include <dali/devel-api/common/stage-devel.h>
29 #include <dali/devel-api/update/frame-callback-interface.h>
33 namespace Dali::Toolkit::ParticleSystem::Internal
35 constexpr uint32_t DEFAULT_PARTICLE_COUNT = 100u; ///< Default number of particles in system if not set by user
38 * Particle system frame callback to run modifiers and sources
40 class FrameCallback : public Dali::FrameCallbackInterface
46 FrameCallback(Internal::ParticleEmitter* emitter)
51 ~FrameCallback() = default;
54 bool Update(Dali::UpdateProxy& updateProxy, float elapsedSeconds) override
60 Internal::ParticleEmitter* mEmitter;
63 ParticleSystem::ParticleSource ParticleEmitter::GetSource() const
65 return mParticleSource;
68 void ParticleEmitter::SetSource(const ParticleSystem::ParticleSource& source)
70 mParticleStatusBits |= SOURCE_SET_STATUS_BIT;
71 mParticleSource = source;
73 // call the init function of source
74 GetImplementation(mParticleSource).GetUpdater().Init();
77 void ParticleEmitter::SetDomain(const ParticleSystem::ParticleDomain& domain)
79 mParticleStatusBits |= DOMAIN_SET_STATUS_BIT;
80 mParticleDomain = domain;
83 void ParticleEmitter::SetRenderer(const ParticleSystem::ParticleRenderer& renderer)
85 mParticleStatusBits |= RENDERER_SET_STATUS_BIT;
86 mParticleRenderer = renderer;
87 GetImplementation(mParticleRenderer).SetEmitter(this);
90 void ParticleEmitter::SetParticleCount(uint32_t maxParticleCount)
92 if(!mParticleList || maxParticleCount != GetImplementation(mParticleList).GetParticleCount())
94 // Default particle list has no data streams, it will replace old list
95 mParticleList = ParticleSystem::ParticleList::New(maxParticleCount,
96 ParticleStream::POSITION_STREAM_BIT |
97 ParticleStream::COLOR_STREAM_BIT |
98 ParticleStream::VELOCITY_STREAM_BIT |
99 ParticleStream::SCALE_STREAM_BIT |
100 ParticleStream::LIFETIME_STREAM_BIT);
104 uint32_t ParticleEmitter::GetParticleCount()
106 return GetImplementation(mParticleList).GetParticleCount();
109 ParticleSystem::ParticleList& ParticleEmitter::GetParticleList()
111 return mParticleList;
114 uint32_t ParticleEmitter::AddModifier(const ParticleSystem::ParticleModifier& modifier)
116 mModifiers.emplace_back(modifier);
117 return mModifiers.size() - 1;
120 ParticleSystem::ParticleDomain ParticleEmitter::GetDomain() const
122 return mParticleDomain;
125 ParticleSystem::ParticleRenderer ParticleEmitter::GetRenderer() const
127 return mParticleRenderer;
130 ParticleSystem::ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
132 return index < mModifiers.size() ? mModifiers[index] : ParticleSystem::ParticleModifier();
135 void ParticleEmitter::RemoveModifierAt(uint32_t index)
137 mModifiers.erase(mModifiers.begin() + index);
140 void ParticleEmitter::Start()
142 if(mActor && IsComplete() && !(mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
149 GetImplementation(mParticleRenderer).Initialize();
151 mSystemStarted = true;
152 mParticleStatusBits &= ~(SIMULATION_STOPPED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
153 mParticleStatusBits |= SIMULATION_STARTED_STATUS_BIT;
154 mFrameCallback = std::make_unique<FrameCallback>(this);
156 // Attach renderer to an actor
157 auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
158 mActor.AddRenderer(renderer);
159 DevelStage::AddFrameCallback(Stage::GetCurrent(), *mFrameCallback, mActor);
163 void ParticleEmitter::Stop()
165 if(mActor && IsComplete() && (mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
167 mSystemStarted = false;
168 mParticleStatusBits &= ~(SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
169 mParticleStatusBits |= SIMULATION_STOPPED_STATUS_BIT;
170 auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
171 mActor.RemoveRenderer(renderer);
172 DevelStage::RemoveFrameCallback(Stage::GetCurrent(), *mFrameCallback);
176 std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
178 std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(
179 std::chrono::system_clock::now().time_since_epoch());
183 void ParticleEmitter::Update()
185 // Do not update if emitter setup isn't complete
191 auto ms = GetCurrentTimeMillis();
193 if(mCurrentMilliseconds.count() == 0)
195 mCurrentMilliseconds = ms;
198 if(mLastUpdateMs.count() == 0)
203 float emissionDelta = 1.0f / float(mEmissionRatePerSecond); // time per one particle emission (TODO: add some randomness to it)
205 auto diffTime = double((ms - mCurrentMilliseconds).count()) / 1000.0;
207 uint32_t emissionCount = 0u;
208 if(diffTime >= emissionDelta)
210 emissionCount = round(diffTime / emissionDelta);
211 mCurrentMilliseconds = ms;
214 // Update lifetimes and discard dead particles
215 auto& particles = mParticleList.GetActiveParticles();
216 auto dt = ms - mLastUpdateMs;
219 std::vector<int> toErase;
221 for(auto& p : particles)
223 auto& lifetime = p.Get<float>(ParticleStream::LIFETIME_STREAM_BIT);
224 lifetime -= (float(dt.count()) / 1000.0f);
227 toErase.emplace_back(n);
235 for(auto& v : toErase)
237 GetImplementation(mParticleList).ReleaseParticle(v - indexShift);
244 // apply initial emission count
247 emissionCount = mEmissionCountOnStart;
248 mSystemStarted = false;
251 // Update source if there are any particles to be emitted
254 // Apply active particles limiter
255 if(mActiveParticlesLimit && mParticleList.GetActiveParticleCount() + emissionCount > mActiveParticlesLimit)
257 emissionCount = mActiveParticlesLimit - mParticleList.GetActiveParticleCount();
259 UpdateSource(emissionCount);
262 // Update modifier stack
263 for(auto& modifier : mModifiers)
267 // Parallel processing must be enabled in order to use MT mode
268 bool mt = GetImplementation(modifier).GetUpdater().IsMultiThreaded() && mParallelProcessing;
270 if(!mt) // single-threaded, update all particles in one go
272 GetImplementation(modifier).Update(mParticleList, 0, mParticleList.GetActiveParticleCount());
276 UpdateModifierMT(modifier);
284 void ParticleEmitter::AttachTo(Actor actor)
286 mActor = std::move(actor);
289 Actor ParticleEmitter::GetActor() const
294 void ParticleEmitter::UpdateSource(uint32_t count)
296 GetImplementation(mParticleSource).Update(mParticleList, count);
299 void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier)
301 auto& threadPool = GetThreadPool();
302 auto workerThreads = threadPool.GetWorkerCount();
303 auto activeCount = mParticleList.GetActiveParticleCount();
305 // at least 10 particles per worker thread (should be parametrized)
306 // If less, continue ST
307 if(activeCount < workerThreads * 10)
309 GetImplementation(modifier).Update(mParticleList, 0, activeCount);
313 auto partial = mParticleList.GetActiveParticleCount() / workerThreads;
318 UpdateTask(Internal::ParticleModifier& modifier, ParticleSystem::ParticleList& list, uint32_t first, uint32_t count)
319 : mModifier(modifier),
326 Internal::ParticleModifier& mModifier;
327 ParticleSystem::ParticleList& mList;
333 mModifier.Update(mList, mFirst, mCount);
337 std::vector<UpdateTask> updateTasks;
338 updateTasks.reserve(workerThreads);
339 std::vector<Task> tasks;
341 for(auto i = 0u; i < workerThreads; ++i)
343 auto index = i * partial;
344 auto count = partial;
345 if(i == workerThreads - 1 && index + count < activeCount)
347 count = activeCount - index;
350 updateTasks.emplace_back(GetImplementation(modifier), mParticleList, index, count);
351 tasks.emplace_back([&task = updateTasks.back()](uint32_t n) { task.Update(); });
354 auto future = threadPool.SubmitTasks(tasks, 0);
358 void ParticleEmitter::UpdateDomain()
363 void ParticleEmitter::SetEmissionRate(uint32_t ratePerSecond)
365 mEmissionRatePerSecond = ratePerSecond;
368 uint32_t ParticleEmitter::GetEmissionRate() const
370 return mEmissionRatePerSecond;
373 void ParticleEmitter::EnableParallelProcessing(bool enabled)
375 mParallelProcessing = enabled;
378 bool ParticleEmitter::IsParallelProcessingEnabled() const
380 return mParallelProcessing;
383 void ParticleEmitter::SetInitialParticleCount(uint32_t count)
385 mEmissionCountOnStart = count;
388 uint32_t ParticleEmitter::GetInitialParticleCount() const
390 return mEmissionCountOnStart;
393 void ParticleEmitter::SetActiveParticlesLimit(uint32_t count)
395 mActiveParticlesLimit = count;
398 uint32_t ParticleEmitter::GetActiveParticlesLimit() const
400 return mActiveParticlesLimit;
403 ParticleSystem::ParticleEmitter::Status ParticleEmitter::GetStatus() const
405 auto statusMask = SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT | SIMULATION_STOPPED_STATUS_BIT;
406 auto status = (mParticleStatusBits & statusMask);
408 if(status & SIMULATION_PAUSED_STATUS_BIT)
410 return ParticleSystem::ParticleEmitter::Status::PAUSED;
412 else if(status & SIMULATION_STOPPED_STATUS_BIT)
414 return ParticleSystem::ParticleEmitter::Status::STOPPED;
416 else if(status & SIMULATION_STARTED_STATUS_BIT)
418 return ParticleSystem::ParticleEmitter::Status::STARTED;
422 return !IsComplete() ? ParticleSystem::ParticleEmitter::Status::INCOMPLETE : ParticleSystem::ParticleEmitter::Status::READY;
426 ParticleEmitter::ParticleEmitter()
428 // Necessary to be called to initialize internal ParticleList
429 SetParticleCount(DEFAULT_PARTICLE_COUNT);
432 ParticleEmitter::~ParticleEmitter()
434 if(mParticleRenderer)
436 GetImplementation(mParticleRenderer).PrepareToDie();
440 } // namespace Dali::Toolkit::ParticleSystem::Internal
441 namespace Dali::Toolkit::ParticleSystem
443 Dali::ThreadPool& GetThreadPool()
445 static std::unique_ptr<Dali::ThreadPool> gThreadPool{nullptr};
446 static std::once_flag onceFlag;
448 // Intialize thread pool if not there yet, make sure it happens once and it's synchronized!,
449 // NOTE: this function shouldn't be called from multiple thread anyway
452 std::call_once(onceFlag, [&threadPool = gThreadPool] { threadPool = std::make_unique<Dali::ThreadPool>();
453 threadPool->Initialize(4u); });
458 } // namespace Dali::Toolkit::ParticleSystem