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
36 constexpr uint32_t DEFAULT_PARTICLE_COUNT = 100u; ///< Default number of particles in system if not set by user
39 * Particle system frame callback to run modifiers and sources
41 class FrameCallback : public Dali::FrameCallbackInterface
47 FrameCallback(Internal::ParticleEmitter* emitter)
52 ~FrameCallback() = default;
55 void 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 // Default particle list has no data streams, it will replace old list
93 mParticleList = ParticleSystem::ParticleList::New(maxParticleCount,
94 ParticleStream::POSITION_STREAM_BIT |
95 ParticleStream::COLOR_STREAM_BIT |
96 ParticleStream::VELOCITY_STREAM_BIT |
97 ParticleStream::SCALE_STREAM_BIT |
98 ParticleStream::LIFETIME_STREAM_BIT);
101 ParticleSystem::ParticleList& ParticleEmitter::GetParticleList()
103 return mParticleList;
106 uint32_t ParticleEmitter::AddModifier(const ParticleSystem::ParticleModifier& modifier)
108 mModifiers.emplace_back(modifier);
109 return mModifiers.size() - 1;
112 ParticleSystem::ParticleDomain ParticleEmitter::GetDomain() const
114 return mParticleDomain;
117 ParticleSystem::ParticleRenderer ParticleEmitter::GetRenderer() const
119 return mParticleRenderer;
122 ParticleSystem::ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
124 return index < mModifiers.size() ? mModifiers[index] : ParticleSystem::ParticleModifier();
127 void ParticleEmitter::RemoveModifierAt(uint32_t index)
129 mModifiers.erase(mModifiers.begin()+index);
132 void ParticleEmitter::Start()
134 if(mActor && IsComplete() && !(mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
141 GetImplementation(mParticleRenderer).Initialize();
143 mSystemStarted = true;
144 mParticleStatusBits &= ~(SIMULATION_STOPPED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
145 mParticleStatusBits |= SIMULATION_STARTED_STATUS_BIT;
146 mFrameCallback = std::make_unique<FrameCallback>(this);
148 // Attach renderer to an actor
149 auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
150 mActor.AddRenderer(renderer);
151 DevelStage::AddFrameCallback(Stage::GetCurrent(), *mFrameCallback, mActor);
155 void ParticleEmitter::Stop()
157 if(mActor && IsComplete() && (mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
159 mSystemStarted = false;
160 mParticleStatusBits &= ~(SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
161 mParticleStatusBits |= SIMULATION_STOPPED_STATUS_BIT;
162 auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
163 mActor.RemoveRenderer(renderer);
164 DevelStage::RemoveFrameCallback(Stage::GetCurrent(), *mFrameCallback);
168 std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
170 std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(
171 std::chrono::system_clock::now().time_since_epoch());
175 void ParticleEmitter::Update()
177 // Do not update if emitter setup isn't complete
183 auto ms = GetCurrentTimeMillis();
185 if(mCurrentMilliseconds.count() == 0)
187 mCurrentMilliseconds = ms;
190 if(mLastUpdateMs.count() == 0)
195 float emissionDelta = 1.0f / float(mEmissionRatePerSecond); // time per one particle emission (TODO: add some randomness to it)
197 auto diffTime = double((ms - mCurrentMilliseconds).count()) / 1000.0;
199 uint32_t emissionCount = 0u;
200 if(diffTime >= emissionDelta)
202 emissionCount = round(diffTime / emissionDelta);
203 mCurrentMilliseconds = ms;
206 // Update lifetimes and discard dead particles
207 auto& particles = mParticleList.GetActiveParticles();
208 auto dt = ms - mLastUpdateMs;
211 std::vector<int> toErase;
213 for(auto& p : particles)
215 auto& lifetime = p.Get<float>(ParticleStream::LIFETIME_STREAM_BIT);
216 lifetime -= (float(dt.count()) / 1000.0f);
219 toErase.emplace_back(n);
227 for(auto& v : toErase)
229 GetImplementation(mParticleList).ReleaseParticle(v - indexShift);
236 // apply initial emission count
239 emissionCount = mEmissionCountOnStart;
240 mSystemStarted = false;
243 // Update source if there are any particles to be emitted
246 // Apply active particles limiter
247 if(mActiveParticlesLimit && mParticleList.GetActiveParticleCount() + emissionCount > mActiveParticlesLimit)
249 emissionCount = mActiveParticlesLimit - mParticleList.GetActiveParticleCount();
251 UpdateSource(emissionCount);
254 // Update modifier stack
255 for(auto& modifier : mModifiers)
259 // Parallel processing must be enabled in order to use MT mode
260 bool mt = GetImplementation(modifier).GetUpdater().IsMultiThreaded() && mParallelProcessing;
262 if(!mt) // single-threaded, update all particles in one go
264 GetImplementation(modifier).Update(mParticleList, 0, mParticleList.GetActiveParticleCount());
268 UpdateModifierMT(modifier);
276 void ParticleEmitter::AttachTo(Actor actor)
278 mActor = std::move(actor);
281 Actor ParticleEmitter::GetActor() const
286 void ParticleEmitter::UpdateSource(uint32_t count)
288 GetImplementation(mParticleSource).Update(mParticleList, count);
291 void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier)
293 auto& threadPool = GetThreadPool();
294 auto workerThreads = threadPool.GetWorkerCount();
295 auto activeCount = mParticleList.GetActiveParticleCount();
297 // at least 10 particles per worker thread (should be parametrized)
298 // If less, continue ST
299 if(activeCount < workerThreads * 10)
301 GetImplementation(modifier).Update(mParticleList, 0, activeCount);
305 auto partial = mParticleList.GetActiveParticleCount() / workerThreads;
310 UpdateTask(Internal::ParticleModifier& modifier, ParticleSystem::ParticleList& list, uint32_t first, uint32_t count)
311 : mModifier(modifier),
318 Internal::ParticleModifier& mModifier;
319 ParticleSystem::ParticleList& mList;
325 mModifier.Update(mList, mFirst, mCount);
329 std::vector<UpdateTask> updateTasks;
330 updateTasks.reserve(workerThreads);
331 std::vector<Task> tasks;
333 for(auto i = 0u; i < workerThreads; ++i)
335 auto index = i * partial;
336 auto count = partial;
337 if(i == workerThreads - 1 && index + count < activeCount)
339 count = activeCount - index;
342 updateTasks.emplace_back(GetImplementation(modifier), mParticleList, index, count);
343 tasks.emplace_back([&task = updateTasks.back()](uint32_t n)
345 //printf("Updating modifier: %d\n", n);
349 auto future = threadPool.SubmitTasks(tasks, 0);
353 void ParticleEmitter::UpdateDomain()
358 void ParticleEmitter::SetEmissionRate(uint32_t ratePerSecond)
360 mEmissionRatePerSecond = ratePerSecond;
363 uint32_t ParticleEmitter::GetEmissionRate() const
365 return mEmissionRatePerSecond;
368 void ParticleEmitter::EnableParallelProcessing(bool enabled)
370 mParallelProcessing = enabled;
373 bool ParticleEmitter::IsParallelProcessingEnabled() const
375 return mParallelProcessing;
378 void ParticleEmitter::SetInitialParticleCount(uint32_t count)
380 mEmissionCountOnStart = count;
383 uint32_t ParticleEmitter::GetInitialParticleCount() const
385 return mEmissionCountOnStart;
388 void ParticleEmitter::SetActiveParticlesLimit(uint32_t count)
390 mActiveParticlesLimit = count;
393 uint32_t ParticleEmitter::GetActiveParticlesLimit() const
395 return mActiveParticlesLimit;
398 ParticleSystem::ParticleEmitter::Status ParticleEmitter::GetStatus() const
400 auto statusMask = SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT | SIMULATION_STOPPED_STATUS_BIT;
401 auto status = (mParticleStatusBits & statusMask);
403 if(status & SIMULATION_PAUSED_STATUS_BIT)
405 return ParticleSystem::ParticleEmitter::Status::PAUSED;
407 else if(status & SIMULATION_STOPPED_STATUS_BIT)
409 return ParticleSystem::ParticleEmitter::Status::STOPPED;
411 else if(status & SIMULATION_STARTED_STATUS_BIT)
413 return ParticleSystem::ParticleEmitter::Status::STARTED;
417 return !IsComplete() ? ParticleSystem::ParticleEmitter::Status::INCOMPLETE : ParticleSystem::ParticleEmitter::Status::READY;
421 ParticleEmitter::ParticleEmitter()
423 // Necessary to be called to initialize internal ParticleList
424 SetParticleCount(DEFAULT_PARTICLE_COUNT);
427 } // namespace Dali::Toolkit::ParticleSystem::Internal
428 namespace Dali::Toolkit::ParticleSystem
430 Dali::ThreadPool& GetThreadPool()
432 static std::unique_ptr<Dali::ThreadPool> gThreadPool{nullptr};
433 static std::once_flag onceFlag;
435 // Intialize thread pool if not there yet, make sure it happens once and it's synchronized!,
436 // NOTE: this function shouldn't be called from multiple thread anyway
439 std::call_once(onceFlag, [&threadPool = gThreadPool]
440 { threadPool = std::make_unique<Dali::ThreadPool>();
441 threadPool->Initialize(4u); });
446 } // namespace Dali::Toolkit::ParticleSystem