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 void Update(Dali::UpdateProxy& updateProxy, float elapsedSeconds) override
59 Internal::ParticleEmitter* mEmitter;
62 ParticleSystem::ParticleSource ParticleEmitter::GetSource() const
64 return mParticleSource;
67 void ParticleEmitter::SetSource(const ParticleSystem::ParticleSource& source)
69 mParticleStatusBits |= SOURCE_SET_STATUS_BIT;
70 mParticleSource = source;
72 // call the init function of source
73 GetImplementation(mParticleSource).GetUpdater().Init();
76 void ParticleEmitter::SetDomain(const ParticleSystem::ParticleDomain& domain)
78 mParticleStatusBits |= DOMAIN_SET_STATUS_BIT;
79 mParticleDomain = domain;
82 void ParticleEmitter::SetRenderer(const ParticleSystem::ParticleRenderer& renderer)
84 mParticleStatusBits |= RENDERER_SET_STATUS_BIT;
85 mParticleRenderer = renderer;
86 GetImplementation(mParticleRenderer).SetEmitter(this);
89 void ParticleEmitter::SetParticleCount(uint32_t maxParticleCount)
91 // Default particle list has no data streams, it will replace old list
92 mParticleList = ParticleSystem::ParticleList::New(maxParticleCount,
93 ParticleStream::POSITION_STREAM_BIT |
94 ParticleStream::COLOR_STREAM_BIT |
95 ParticleStream::VELOCITY_STREAM_BIT |
96 ParticleStream::SCALE_STREAM_BIT |
97 ParticleStream::LIFETIME_STREAM_BIT);
100 ParticleSystem::ParticleList& ParticleEmitter::GetParticleList()
102 return mParticleList;
105 uint32_t ParticleEmitter::AddModifier(const ParticleSystem::ParticleModifier& modifier)
107 mModifiers.emplace_back(modifier);
108 return mModifiers.size() - 1;
111 ParticleSystem::ParticleDomain ParticleEmitter::GetDomain() const
113 return mParticleDomain;
116 ParticleSystem::ParticleRenderer ParticleEmitter::GetRenderer() const
118 return mParticleRenderer;
121 ParticleSystem::ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
123 return index < mModifiers.size() ? mModifiers[index] : ParticleSystem::ParticleModifier();
126 void ParticleEmitter::RemoveModifierAt(uint32_t index)
128 mModifiers.erase(mModifiers.begin() + index);
131 void ParticleEmitter::Start()
133 if(mActor && IsComplete() && !(mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
140 GetImplementation(mParticleRenderer).Initialize();
142 mSystemStarted = true;
143 mParticleStatusBits &= ~(SIMULATION_STOPPED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
144 mParticleStatusBits |= SIMULATION_STARTED_STATUS_BIT;
145 mFrameCallback = std::make_unique<FrameCallback>(this);
147 // Attach renderer to an actor
148 auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
149 mActor.AddRenderer(renderer);
150 DevelStage::AddFrameCallback(Stage::GetCurrent(), *mFrameCallback, mActor);
154 void ParticleEmitter::Stop()
156 if(mActor && IsComplete() && (mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
158 mSystemStarted = false;
159 mParticleStatusBits &= ~(SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
160 mParticleStatusBits |= SIMULATION_STOPPED_STATUS_BIT;
161 auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
162 mActor.RemoveRenderer(renderer);
163 DevelStage::RemoveFrameCallback(Stage::GetCurrent(), *mFrameCallback);
167 std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
169 std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(
170 std::chrono::system_clock::now().time_since_epoch());
174 void ParticleEmitter::Update()
176 // Do not update if emitter setup isn't complete
182 auto ms = GetCurrentTimeMillis();
184 if(mCurrentMilliseconds.count() == 0)
186 mCurrentMilliseconds = ms;
189 if(mLastUpdateMs.count() == 0)
194 float emissionDelta = 1.0f / float(mEmissionRatePerSecond); // time per one particle emission (TODO: add some randomness to it)
196 auto diffTime = double((ms - mCurrentMilliseconds).count()) / 1000.0;
198 uint32_t emissionCount = 0u;
199 if(diffTime >= emissionDelta)
201 emissionCount = round(diffTime / emissionDelta);
202 mCurrentMilliseconds = ms;
205 // Update lifetimes and discard dead particles
206 auto& particles = mParticleList.GetActiveParticles();
207 auto dt = ms - mLastUpdateMs;
210 std::vector<int> toErase;
212 for(auto& p : particles)
214 auto& lifetime = p.Get<float>(ParticleStream::LIFETIME_STREAM_BIT);
215 lifetime -= (float(dt.count()) / 1000.0f);
218 toErase.emplace_back(n);
226 for(auto& v : toErase)
228 GetImplementation(mParticleList).ReleaseParticle(v - indexShift);
235 // apply initial emission count
238 emissionCount = mEmissionCountOnStart;
239 mSystemStarted = false;
242 // Update source if there are any particles to be emitted
245 // Apply active particles limiter
246 if(mActiveParticlesLimit && mParticleList.GetActiveParticleCount() + emissionCount > mActiveParticlesLimit)
248 emissionCount = mActiveParticlesLimit - mParticleList.GetActiveParticleCount();
250 UpdateSource(emissionCount);
253 // Update modifier stack
254 for(auto& modifier : mModifiers)
258 // Parallel processing must be enabled in order to use MT mode
259 bool mt = GetImplementation(modifier).GetUpdater().IsMultiThreaded() && mParallelProcessing;
261 if(!mt) // single-threaded, update all particles in one go
263 GetImplementation(modifier).Update(mParticleList, 0, mParticleList.GetActiveParticleCount());
267 UpdateModifierMT(modifier);
275 void ParticleEmitter::AttachTo(Actor actor)
277 mActor = std::move(actor);
280 Actor ParticleEmitter::GetActor() const
285 void ParticleEmitter::UpdateSource(uint32_t count)
287 GetImplementation(mParticleSource).Update(mParticleList, count);
290 void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier)
292 auto& threadPool = GetThreadPool();
293 auto workerThreads = threadPool.GetWorkerCount();
294 auto activeCount = mParticleList.GetActiveParticleCount();
296 // at least 10 particles per worker thread (should be parametrized)
297 // If less, continue ST
298 if(activeCount < workerThreads * 10)
300 GetImplementation(modifier).Update(mParticleList, 0, activeCount);
304 auto partial = mParticleList.GetActiveParticleCount() / workerThreads;
309 UpdateTask(Internal::ParticleModifier& modifier, ParticleSystem::ParticleList& list, uint32_t first, uint32_t count)
310 : mModifier(modifier),
317 Internal::ParticleModifier& mModifier;
318 ParticleSystem::ParticleList& mList;
324 mModifier.Update(mList, mFirst, mCount);
328 std::vector<UpdateTask> updateTasks;
329 updateTasks.reserve(workerThreads);
330 std::vector<Task> tasks;
332 for(auto i = 0u; i < workerThreads; ++i)
334 auto index = i * partial;
335 auto count = partial;
336 if(i == workerThreads - 1 && index + count < activeCount)
338 count = activeCount - index;
341 updateTasks.emplace_back(GetImplementation(modifier), mParticleList, index, count);
342 tasks.emplace_back([&task = updateTasks.back()](uint32_t n) { task.Update(); });
345 auto future = threadPool.SubmitTasks(tasks, 0);
349 void ParticleEmitter::UpdateDomain()
354 void ParticleEmitter::SetEmissionRate(uint32_t ratePerSecond)
356 mEmissionRatePerSecond = ratePerSecond;
359 uint32_t ParticleEmitter::GetEmissionRate() const
361 return mEmissionRatePerSecond;
364 void ParticleEmitter::EnableParallelProcessing(bool enabled)
366 mParallelProcessing = enabled;
369 bool ParticleEmitter::IsParallelProcessingEnabled() const
371 return mParallelProcessing;
374 void ParticleEmitter::SetInitialParticleCount(uint32_t count)
376 mEmissionCountOnStart = count;
379 uint32_t ParticleEmitter::GetInitialParticleCount() const
381 return mEmissionCountOnStart;
384 void ParticleEmitter::SetActiveParticlesLimit(uint32_t count)
386 mActiveParticlesLimit = count;
389 uint32_t ParticleEmitter::GetActiveParticlesLimit() const
391 return mActiveParticlesLimit;
394 ParticleSystem::ParticleEmitter::Status ParticleEmitter::GetStatus() const
396 auto statusMask = SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT | SIMULATION_STOPPED_STATUS_BIT;
397 auto status = (mParticleStatusBits & statusMask);
399 if(status & SIMULATION_PAUSED_STATUS_BIT)
401 return ParticleSystem::ParticleEmitter::Status::PAUSED;
403 else if(status & SIMULATION_STOPPED_STATUS_BIT)
405 return ParticleSystem::ParticleEmitter::Status::STOPPED;
407 else if(status & SIMULATION_STARTED_STATUS_BIT)
409 return ParticleSystem::ParticleEmitter::Status::STARTED;
413 return !IsComplete() ? ParticleSystem::ParticleEmitter::Status::INCOMPLETE : ParticleSystem::ParticleEmitter::Status::READY;
417 ParticleEmitter::ParticleEmitter()
419 // Necessary to be called to initialize internal ParticleList
420 SetParticleCount(DEFAULT_PARTICLE_COUNT);
423 ParticleEmitter::~ParticleEmitter()
425 if(mParticleRenderer)
427 GetImplementation(mParticleRenderer).PrepareToDie();
431 } // namespace Dali::Toolkit::ParticleSystem::Internal
432 namespace Dali::Toolkit::ParticleSystem
434 Dali::ThreadPool& GetThreadPool()
436 static std::unique_ptr<Dali::ThreadPool> gThreadPool{nullptr};
437 static std::once_flag onceFlag;
439 // Intialize thread pool if not there yet, make sure it happens once and it's synchronized!,
440 // NOTE: this function shouldn't be called from multiple thread anyway
443 std::call_once(onceFlag, [&threadPool = gThreadPool] { threadPool = std::make_unique<Dali::ThreadPool>();
444 threadPool->Initialize(4u); });
449 } // namespace Dali::Toolkit::ParticleSystem