Implementation of DALi Particle System.
Change-Id: I33783236361b31f32460160ea42cb5833b83dc2c
utc-Dali-JsonParser.cpp
utc-Dali-KeyInputFocusManager.cpp
utc-Dali-PageTurnView.cpp
+ utc-Dali-ParticleSystem.cpp
utc-Dali-Scene3dView.cpp
utc-Dali-ScrollBar.cpp
utc-Dali-ScrollView.cpp
ADD_EXECUTABLE(${EXEC_NAME} ${EXEC_NAME}.h ${EXEC_NAME}.cpp ${TC_SOURCES} ${TEST_HARNESS_SOURCES})
TARGET_LINK_LIBRARIES(${EXEC_NAME}
${${CAPI_LIB}_LIBRARIES}
- -lpthread --coverage
+ -lpthread --coverage -ldl
)
INSTALL(PROGRAMS ${EXEC_NAME}
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/public-api/particle-system/particle-emitter.h>
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+#include <dali-toolkit/public-api/particle-system/particle.h>
+#include <dali-test-suite-utils.h>
+
+#include <dlfcn.h>
+
+#include <future>
+
+using namespace Dali;
+using namespace Dali::Toolkit::ParticleSystem;
+
+/**
+ * Helper function to invoke next function in the call chain
+ */
+template<class R, class T, class... Args>
+R InvokeNext(T* pObj, Args... args)
+{
+ Dl_info info;
+ dladdr(__builtin_return_address(0), &info);
+ using Func = R(*)(T*,Args...);
+ auto sym = (Func)(dlsym(RTLD_NEXT, info.dli_sname));
+ return sym(pObj, args...);
+}
+
+// Create fake time getter
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+struct ParticleEmitter
+{
+ [[nodiscard]] std::chrono::milliseconds GetCurrentTimeMillis() const;
+
+ static std::chrono::milliseconds currentTime;
+
+ static void AdvanceTimeByMs( uint32_t ms)
+ {
+ currentTime += std::chrono::milliseconds(ms);
+ }
+};
+
+std::chrono::milliseconds ParticleEmitter::currentTime(1u);
+
+std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
+{
+ [[maybe_unused]] auto value = InvokeNext<std::chrono::milliseconds>(this);
+ return std::chrono::milliseconds(currentTime);
+}
+}
+
+using ParticleEmitterWrapper = Dali::Toolkit::ParticleSystem::Internal::ParticleEmitter;
+
+Texture CreateTexture()
+{
+ Texture texture = Texture::New(Dali::TextureType::TEXTURE_2D, Dali::Pixel::RGBA8888, 100, 100);
+ uint8_t* data = reinterpret_cast<uint8_t*>(malloc(100*100*4));
+ PixelData pixelData = PixelData::New(data, 100*100*4, 100, 100, Pixel::Format::RGBA8888, PixelData::FREE);
+ texture.Upload(pixelData);
+ return texture;
+}
+
+
+/**
+ * Test particle source
+ */
+class TestSource : public ParticleSourceInterface
+{
+public:
+
+ TestSource(ParticleEmitter* emitter)
+ {
+ mEmitter = *emitter;
+ }
+
+ void NewFrame()
+ {
+ mPromise = std::promise<uint32_t>();
+ mFuture = mPromise.get_future();
+ }
+
+ uint32_t Update(ParticleList& outList, uint32_t count) override
+ {
+ mPromise.set_value(count);
+ auto i = count;
+ while(--i)
+ {
+ outList.NewParticle(1.0f);
+ }
+ return count;
+ }
+
+ void Init() override
+ {
+ // calls initialized
+ mInitialized = true;
+ }
+
+ bool mInitialized{false};
+ std::future<uint32_t> mFuture;
+ std::promise<uint32_t> mPromise;
+ ParticleEmitter mEmitter;
+};
+
+class TestSource2 : public ParticleSourceInterface
+{
+public:
+
+ TestSource2(ParticleEmitter* emitter)
+ {
+ mEmitter = *emitter;
+ }
+
+ void NewFrame()
+ {
+ mPromise = std::promise<uint32_t>();
+ mFuture = mPromise.get_future();
+ }
+
+ uint32_t Update(ParticleList& outList, uint32_t count) override
+ {
+ mPromise.set_value(count);
+
+ while(count--)
+ {
+ auto particle = outList.NewParticle(1.0);
+ if(!particle)
+ {
+ return 0u;
+ }
+
+ [[maybe_unused]] auto& pos = particle.GetByIndex<Vector3>(mStreamBasePos);
+
+ [[maybe_unused]] auto& gpos = particle.Get<Vector3>(ParticleStream::POSITION_STREAM_BIT);
+ [[maybe_unused]] auto& col = particle.Get<Vector4>(ParticleStream::COLOR_STREAM_BIT);
+ [[maybe_unused]] auto& vel = particle.Get<Vector3>(ParticleStream::VELOCITY_STREAM_BIT);
+ [[maybe_unused]] auto& sca = particle.Get<Vector3>(ParticleStream::SCALE_STREAM_BIT);
+ //auto& basePos = particle.Get<Vector3>(ParticleStream::SCALE_STREAM_BIT);
+ }
+
+ return count;
+ }
+
+ void Init() override
+ {
+ // calls initialized
+ mStreamBasePos = mEmitter.GetParticleList().AddLocalStream<Vector3>(Vector3::ZERO);
+ mInitialized = true;
+ }
+
+ bool mInitialized{false};
+ std::future<uint32_t> mFuture;
+ std::promise<uint32_t> mPromise;
+ uint32_t mStreamBasePos{0u};
+ ParticleEmitter mEmitter;
+
+};
+/**
+ * Sample of FlameModifier
+ */
+struct TestModifier : public ParticleModifierInterface
+{
+ void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) override
+ {
+
+ }
+};
+
+struct TestModifierMT : public ParticleModifierInterface
+{
+ void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) override
+ {
+
+ }
+
+ bool IsMultiThreaded() override
+ {
+ return true;
+ }
+};
+
+/**
+ * Another modifier to test modifier stack
+ */
+struct TestModifier2 : public ParticleModifierInterface
+{
+ void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) override
+ {
+
+ }
+};
+
+struct EmitterGroup
+{
+ ParticleEmitter emitter;
+ ParticleRenderer renderer;
+ ParticleModifier modifier;
+ ParticleSource source;
+};
+
+// Helper function to create emitter (every test will be doing that)
+template<class SOURCE, class MODIFIER>
+ParticleEmitter CreateEmitter(EmitterGroup* output = nullptr)
+{
+ auto emitter = ParticleEmitter::New();
+
+ bool result = (emitter != nullptr);
+ DALI_TEST_EQUALS( result, true, TEST_LOCATION );
+
+ // Create test source
+ auto source = ParticleSource::New<SOURCE>(&emitter);
+
+ {
+ BaseHandle handle(source);
+ auto newHandle = ParticleSource::DownCast(handle);
+ DALI_TEST_EQUALS(newHandle, source, TEST_LOCATION);
+ }
+
+ // Create test renderer
+ auto renderer = ParticleRenderer::New();
+
+ {
+ BaseHandle handle(renderer);
+ auto newHandle = ParticleRenderer::DownCast(handle);
+ DALI_TEST_EQUALS(newHandle, renderer, TEST_LOCATION);
+ }
+
+ // Create modifier
+ auto modifier = ParticleModifier::New<MODIFIER>();
+
+ {
+ BaseHandle handle(modifier);
+ auto newHandle = ParticleModifier::DownCast(handle);
+ DALI_TEST_EQUALS(newHandle, modifier, TEST_LOCATION);
+ }
+
+ auto domain = ParticleDomain::New();
+
+ {
+ BaseHandle handle(domain);
+ auto newHandle = ParticleDomain::DownCast(handle);
+ DALI_TEST_EQUALS(newHandle, domain, TEST_LOCATION);
+ }
+
+ // Test emitter readiness
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::INCOMPLETE, TEST_LOCATION);
+
+ // Attach all components to the emitter
+ emitter.SetSource( source );
+ emitter.SetRenderer( renderer );
+ emitter.AddModifier( modifier );
+ emitter.SetDomain( domain );
+
+ auto domain0 = emitter.GetDomain();
+ auto renderer0 = emitter.GetRenderer();
+
+ DALI_TEST_EQUALS( renderer0, renderer, TEST_LOCATION);
+ DALI_TEST_EQUALS( domain0, domain, TEST_LOCATION);
+
+ if(output)
+ {
+ output->emitter = emitter;
+ output->renderer = renderer;
+ output->modifier = modifier;
+ output->source = source;
+ }
+
+ return emitter;
+}
+
+int UtcDaliParticleSystemEmitterNew(void)
+{
+ // create particle emitter
+ auto emitter = ParticleEmitter::New();
+
+ bool result = (emitter != nullptr);
+ DALI_TEST_EQUALS( result, true, TEST_LOCATION );
+
+ // Create test source
+ auto source = ParticleSource::New<TestSource>(&emitter);
+
+ // Create test renderer
+ auto renderer = ParticleRenderer::New();
+
+ // Create modifier
+ auto modifier = ParticleModifier::New<TestModifier>();
+
+ // Create domain
+ auto domain = ParticleDomain::New();
+
+ // Test emitter readiness
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::INCOMPLETE, TEST_LOCATION);
+
+ // Attach all components to the emitter
+ emitter.SetSource( source );
+ emitter.SetRenderer( renderer );
+ emitter.AddModifier( modifier );
+ emitter.SetDomain( domain );
+
+ // test status again (domain is optional);
+ ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ END_TEST;
+}
+
+int UtcDaliParticleSystemEmitterModifierStack(void)
+{
+ // create particle emitter
+ auto emitter = ParticleEmitter::New();
+
+ bool result = (emitter != nullptr);
+ DALI_TEST_EQUALS( result, true, TEST_LOCATION );
+
+ // Create test source
+ auto source = ParticleSource::New<TestSource>(&emitter);
+
+ // Create test renderer
+ auto renderer = ParticleRenderer::New();
+
+ // Create modifier
+ auto modifier0 = ParticleModifier::New<TestModifier>();
+ auto modifier1 = ParticleModifier::New<TestModifier>();
+ auto modifier2 = ParticleModifier::New<TestModifier>();
+
+ // Create domain
+ auto domain = ParticleDomain::New();
+
+ // Test emitter readiness
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::INCOMPLETE, TEST_LOCATION);
+
+ // Attach all components to the emitter
+ emitter.SetSource( source );
+ emitter.SetRenderer( renderer );
+ emitter.AddModifier( modifier0 );
+ emitter.AddModifier( modifier1 );
+ emitter.AddModifier( modifier2 );
+
+ emitter.SetDomain( domain );
+
+ // test status again (domain is optional);
+ ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ auto modifier = emitter.GetModifierAt(1);
+ DALI_TEST_EQUALS(modifier, modifier1, TEST_LOCATION);
+
+ emitter.RemoveModifierAt(0);
+ modifier = emitter.GetModifierAt(0);
+ DALI_TEST_EQUALS(modifier, modifier1, TEST_LOCATION);
+
+ END_TEST;
+}
+
+int UtcDaliParticleSystemTest(void)
+{
+ TestApplication application;
+
+ // Create actor to be used with emitter
+ Actor actor = Actor::New();
+ application.GetScene().Add(actor);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+ auto emitter = CreateEmitter<TestSource, TestModifier>();
+
+ // test status again (domain is optional);
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ // Set initial parameters of system
+ emitter.SetInitialParticleCount( 1000 );
+ emitter.SetActiveParticlesLimit( 5000 );
+
+ // Test getters
+ auto initialParticleCount = emitter.GetInitialParticleCount();
+ auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+ // Attach emitter to actor
+ emitter.AttachTo(actor);
+
+ // Start emitter
+ emitter.Start();
+
+ auto status = emitter.GetStatus();
+ DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+ auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+ // Run simulation
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ // First call into source callback should emit initial number of particles
+ auto emittedParticleCount = sourceCallback.mFuture.get();
+ DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+ // Run 3 more frames advancing by 1000ms which should
+ // emit particles based on emission rate
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
+
+int UtcDaliParticleSystemTestWithTextureScreen(void)
+{
+ TestApplication application;
+
+ // Create actor to be used with emitter
+ Actor actor = Actor::New();
+ application.GetScene().Add(actor);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+ EmitterGroup group;
+
+ auto emitter = CreateEmitter<TestSource, TestModifier>(&group);
+
+ // Blending mode with screen
+ auto texture = CreateTexture();
+ group.renderer.SetTexture( texture );
+ group.renderer.SetBlendingMode( BlendingMode::SCREEN );
+
+ // test status again (domain is optional);
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ // Set initial parameters of system
+ emitter.SetInitialParticleCount( 1000 );
+ emitter.SetActiveParticlesLimit( 5000 );
+
+ // Test getters
+ auto initialParticleCount = emitter.GetInitialParticleCount();
+ auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+ // Attach emitter to actor
+ emitter.AttachTo(actor);
+
+ // Start emitter
+ emitter.Start();
+
+ auto status = emitter.GetStatus();
+ DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+ auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+ // Run simulation
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ // First call into source callback should emit initial number of particles
+ auto emittedParticleCount = sourceCallback.mFuture.get();
+ DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+ // Run 3 more frames advancing by 1000ms which should
+ // emit particles based on emission rate
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
+
+int UtcDaliParticleSystemTestWithTextureAdd(void)
+{
+ TestApplication application;
+
+ // Create actor to be used with emitter
+ Actor actor = Actor::New();
+ application.GetScene().Add(actor);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+ EmitterGroup group;
+
+ auto emitter = CreateEmitter<TestSource, TestModifier>(&group);
+
+ // Blending mode with screen
+ auto texture = CreateTexture();
+ group.renderer.SetTexture( texture );
+ group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+
+ // test status again (domain is optional);
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ // Set initial parameters of system
+ emitter.SetInitialParticleCount( 1000 );
+ emitter.SetActiveParticlesLimit( 5000 );
+
+ // Test getters
+ auto initialParticleCount = emitter.GetInitialParticleCount();
+ auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+ // Attach emitter to actor
+ emitter.AttachTo(actor);
+
+ // Start emitter
+ emitter.Start();
+
+ auto status = emitter.GetStatus();
+ DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+ auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+ // Run simulation
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ // First call into source callback should emit initial number of particles
+ auto emittedParticleCount = sourceCallback.mFuture.get();
+ DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+ // Run 3 more frames advancing by 1000ms which should
+ // emit particles based on emission rate
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
+
+int UtcDaliParticleSystemTestInitialSetup(void)
+{
+ TestApplication application;
+
+ // Create actor to be used with emitter
+ Actor actor = Actor::New();
+ application.GetScene().Add(actor);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+ EmitterGroup group;
+
+ auto emitter = CreateEmitter<TestSource, TestModifier>(&group);
+
+ emitter.SetEmissionRate( 1000 );
+ emitter.SetInitialParticleCount( 1000 );
+ emitter.SetActiveParticlesLimit( 10000 );
+
+ auto emissionRate = emitter.GetEmissionRate();
+ auto initialCount = emitter.GetInitialParticleCount();
+ auto activeCount = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS( emissionRate, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS( initialCount, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS( activeCount, 10000, TEST_LOCATION);
+
+ // Blending mode with screen
+ auto texture = CreateTexture();
+ group.renderer.SetTexture( texture );
+ group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+
+ // test status again (domain is optional);
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ // Set initial parameters of system
+ emitter.SetInitialParticleCount( 1000 );
+ emitter.SetActiveParticlesLimit( 5000 );
+
+ // Test getters
+ auto initialParticleCount = emitter.GetInitialParticleCount();
+ auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+ // Attach emitter to actor
+ emitter.AttachTo(actor);
+
+ // Start emitter
+ emitter.Start();
+
+ auto status = emitter.GetStatus();
+ DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+ auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+ // Run simulation
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ // First call into source callback should emit initial number of particles
+ auto emittedParticleCount = sourceCallback.mFuture.get();
+ DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+ // Run 3 more frames advancing by 1000ms which should
+ // emit particles based on emission rate
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
+
+int UtcDaliParticleSystemTestMT(void)
+{
+ TestApplication application;
+
+ // Create actor to be used with emitter
+ Actor actor = Actor::New();
+ application.GetScene().Add(actor);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+ EmitterGroup group;
+
+ auto emitter = CreateEmitter<TestSource, TestModifierMT>(&group);
+
+ emitter.SetEmissionRate( 10000 );
+ emitter.SetInitialParticleCount( 10000 );
+ emitter.SetActiveParticlesLimit( 20000 );
+ emitter.SetParticleCount( 300000 );
+
+ auto emissionRate = emitter.GetEmissionRate();
+ auto initialCount = emitter.GetInitialParticleCount();
+ auto activeCount = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS( emissionRate, 10000, TEST_LOCATION);
+ DALI_TEST_EQUALS( initialCount, 10000, TEST_LOCATION);
+ DALI_TEST_EQUALS( activeCount, 20000, TEST_LOCATION);
+
+ emitter.EnableParallelProcessing(true);
+
+ auto mtEnabled = emitter.IsParallelProcessingEnabled();
+ DALI_TEST_EQUALS(mtEnabled, true, TEST_LOCATION);
+
+ // Blending mode with screen
+ auto texture = CreateTexture();
+ group.renderer.SetTexture( texture );
+ group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+
+ // test status again (domain is optional);
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ // Attach emitter to actor
+ emitter.AttachTo(actor);
+
+ // Start emitter
+ emitter.Start();
+
+ auto status = emitter.GetStatus();
+ DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+ auto& sourceCallback = dynamic_cast<TestSource&>(emitter.GetSource().GetSourceCallback());
+
+ // Run simulation
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ // First call into source callback should emit initial number of particles
+ auto emittedParticleCount = sourceCallback.mFuture.get();
+ DALI_TEST_EQUALS(emittedParticleCount, 10000, TEST_LOCATION);
+
+ // Run 3 more frames advancing by 1000ms which should
+ // emit particles based on emission rate
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
+
+int UtcDaliParticleSystemTestParticleSource(void)
+{
+ TestApplication application;
+
+ // Create actor to be used with emitter
+ Actor actor = Actor::New();
+ application.GetScene().Add(actor);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
+
+ EmitterGroup group;
+
+ auto emitter = CreateEmitter<TestSource2, TestModifier>(&group);
+
+ emitter.SetEmissionRate( 1000 );
+ emitter.SetInitialParticleCount( 1000 );
+ emitter.SetActiveParticlesLimit( 10000 );
+
+ auto emissionRate = emitter.GetEmissionRate();
+ auto initialCount = emitter.GetInitialParticleCount();
+ auto activeCount = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS( emissionRate, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS( initialCount, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS( activeCount, 10000, TEST_LOCATION);
+
+ emitter.EnableParallelProcessing(true);
+
+ // Blending mode with screen
+ auto texture = CreateTexture();
+ group.renderer.SetTexture( texture );
+ group.renderer.SetBlendingMode( BlendingMode::DEFAULT );
+
+ // test status again (domain is optional);
+ auto ready = emitter.GetStatus();
+
+ // Emitter should return status incomplete
+ DALI_TEST_EQUALS(ready, ParticleEmitter::Status::READY, TEST_LOCATION);
+
+ // Set initial parameters of system
+ emitter.SetInitialParticleCount( 1000 );
+ emitter.SetActiveParticlesLimit( 5000 );
+
+ // Test getters
+ auto initialParticleCount = emitter.GetInitialParticleCount();
+ auto activeParticlesLimit = emitter.GetActiveParticlesLimit();
+
+ DALI_TEST_EQUALS(initialParticleCount, 1000, TEST_LOCATION);
+ DALI_TEST_EQUALS(activeParticlesLimit, 5000, TEST_LOCATION);
+
+ // Attach emitter to actor
+ emitter.AttachTo(actor);
+
+ // Start emitter
+ emitter.Start();
+
+ auto status = emitter.GetStatus();
+ DALI_TEST_EQUALS(status, ParticleEmitter::Status::STARTED, TEST_LOCATION);
+
+ auto& sourceCallback = static_cast<TestSource2&>(emitter.GetSource().GetSourceCallback());
+
+ // Run simulation
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ // First call into source callback should emit initial number of particles
+ auto emittedParticleCount = sourceCallback.mFuture.get();
+ DALI_TEST_EQUALS(emittedParticleCount, 1000, TEST_LOCATION);
+
+ // Run 3 more frames advancing by 1000ms which should
+ // emit particles based on emission rate
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ ParticleEmitterWrapper::AdvanceTimeByMs(1000);
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ // Stop the emitter
+ emitter.Stop();
+
+ sourceCallback.NewFrame();
+ application.SendNotification();
+ application.Render();
+
+ END_TEST;
+}
\ No newline at end of file
${toolkit_src_dir}/builder/style.cpp
${toolkit_src_dir}/builder/tree-node-manipulator.cpp
${toolkit_src_dir}/builder/replacement.cpp
+ ${toolkit_src_dir}/particle-system/particle-impl.cpp
+ ${toolkit_src_dir}/particle-system/particle-domain-impl.cpp
+ ${toolkit_src_dir}/particle-system/particle-emitter-impl.cpp
+ ${toolkit_src_dir}/particle-system/particle-list-impl.cpp
+ ${toolkit_src_dir}/particle-system/particle-modifier-impl.cpp
+ ${toolkit_src_dir}/particle-system/particle-renderer-impl.cpp
+ ${toolkit_src_dir}/particle-system/particle-source-impl.cpp
${toolkit_src_dir}/texture-manager/texture-async-loading-helper.cpp
${toolkit_src_dir}/texture-manager/texture-cache-manager.cpp
${toolkit_src_dir}/texture-manager/texture-manager-impl.cpp
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/particle-system/particle-domain-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+// TODO: This needs physics modifier to be implemented (no use-case yet)
+}
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_DOMAIN_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_DOMAIN_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-object.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleDomain : public Dali::BaseObject
+{
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleDomain& GetImplementation(ParticleSystem::ParticleDomain& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleDomain handle is empty");
+
+ BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<Internal::ParticleDomain&>(handle);
+}
+
+inline const Internal::ParticleDomain& GetImplementation(const ParticleSystem::ParticleDomain& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleDomain handle is empty");
+
+ const BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<const Internal::ParticleDomain&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_DOMAIN_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-modifier-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-source-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/common/stage-devel.h>
+#include <dali/devel-api/update/frame-callback-interface.h>
+#include <memory>
+#include <utility>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+
+constexpr uint32_t DEFAULT_PARTICLE_COUNT = 100u; ///< Default number of particles in system if not set by user
+
+/**
+ * Particle system frame callback to run modifiers and sources
+ */
+class FrameCallback : public Dali::FrameCallbackInterface
+{
+public:
+ /**
+ * @brief Constructor.
+ */
+ FrameCallback(Internal::ParticleEmitter* emitter)
+ : mEmitter(emitter)
+ {
+ }
+
+ ~FrameCallback() = default;
+
+private:
+ void Update(Dali::UpdateProxy& updateProxy, float elapsedSeconds) override
+ {
+ mEmitter->Update();
+ }
+
+ Internal::ParticleEmitter* mEmitter;
+};
+
+ParticleSystem::ParticleSource ParticleEmitter::GetSource() const
+{
+ return mParticleSource;
+}
+
+void ParticleEmitter::SetSource(const ParticleSystem::ParticleSource& source)
+{
+ mParticleStatusBits |= SOURCE_SET_STATUS_BIT;
+ mParticleSource = source;
+
+ // call the init function of source
+ GetImplementation(mParticleSource).GetUpdater().Init();
+}
+
+void ParticleEmitter::SetDomain(const ParticleSystem::ParticleDomain& domain)
+{
+ mParticleStatusBits |= DOMAIN_SET_STATUS_BIT;
+ mParticleDomain = domain;
+}
+
+void ParticleEmitter::SetRenderer(const ParticleSystem::ParticleRenderer& renderer)
+{
+ mParticleStatusBits |= RENDERER_SET_STATUS_BIT;
+ mParticleRenderer = renderer;
+ GetImplementation(mParticleRenderer).SetEmitter(this);
+}
+
+void ParticleEmitter::SetParticleCount(uint32_t maxParticleCount)
+{
+ // Default particle list has no data streams, it will replace old list
+ mParticleList = ParticleSystem::ParticleList::New(maxParticleCount,
+ ParticleStream::POSITION_STREAM_BIT |
+ ParticleStream::COLOR_STREAM_BIT |
+ ParticleStream::VELOCITY_STREAM_BIT |
+ ParticleStream::SCALE_STREAM_BIT |
+ ParticleStream::LIFETIME_STREAM_BIT);
+}
+
+ParticleSystem::ParticleList& ParticleEmitter::GetParticleList()
+{
+ return mParticleList;
+}
+
+uint32_t ParticleEmitter::AddModifier(const ParticleSystem::ParticleModifier& modifier)
+{
+ mModifiers.emplace_back(modifier);
+ return mModifiers.size() - 1;
+}
+
+ParticleSystem::ParticleDomain ParticleEmitter::GetDomain() const
+{
+ return mParticleDomain;
+}
+
+ParticleSystem::ParticleRenderer ParticleEmitter::GetRenderer() const
+{
+ return mParticleRenderer;
+}
+
+ParticleSystem::ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
+{
+ return index < mModifiers.size() ? mModifiers[index] : ParticleSystem::ParticleModifier();
+}
+
+void ParticleEmitter::RemoveModifierAt(uint32_t index)
+{
+ mModifiers.erase(mModifiers.begin()+index);
+}
+
+void ParticleEmitter::Start()
+{
+ if(mActor && IsComplete() && !(mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
+ {
+ if(mFrameCallback)
+ {
+ Stop();
+ }
+
+ GetImplementation(mParticleRenderer).Initialize();
+
+ mSystemStarted = true;
+ mParticleStatusBits &= ~(SIMULATION_STOPPED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
+ mParticleStatusBits |= SIMULATION_STARTED_STATUS_BIT;
+ mFrameCallback = std::make_unique<FrameCallback>(this);
+
+ // Attach renderer to an actor
+ auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
+ mActor.AddRenderer(renderer);
+ DevelStage::AddFrameCallback(Stage::GetCurrent(), *mFrameCallback, mActor);
+ }
+}
+
+void ParticleEmitter::Stop()
+{
+ if(mActor && IsComplete() && (mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT))
+ {
+ mSystemStarted = false;
+ mParticleStatusBits &= ~(SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT);
+ mParticleStatusBits |= SIMULATION_STOPPED_STATUS_BIT;
+ auto renderer = GetImplementation(mParticleRenderer).GetRenderer();
+ mActor.RemoveRenderer(renderer);
+ DevelStage::RemoveFrameCallback(Stage::GetCurrent(), *mFrameCallback);
+ }
+}
+
+std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const
+{
+ std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch());
+ return ms;
+}
+
+void ParticleEmitter::Update()
+{
+ // Do not update if emitter setup isn't complete
+ if(!IsComplete())
+ {
+ return;
+ }
+
+ auto ms = GetCurrentTimeMillis();
+
+ if(mCurrentMilliseconds.count() == 0)
+ {
+ mCurrentMilliseconds = ms;
+ }
+
+ if(mLastUpdateMs.count() == 0)
+ {
+ mLastUpdateMs = ms;
+ }
+
+ 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;
+
+ uint32_t emissionCount = 0u;
+ if(diffTime >= emissionDelta)
+ {
+ emissionCount = round(diffTime / emissionDelta);
+ mCurrentMilliseconds = ms;
+ }
+
+ // Update lifetimes and discard dead particles
+ auto& particles = mParticleList.GetActiveParticles();
+ auto dt = ms - mLastUpdateMs;
+ if(dt.count())
+ {
+ std::vector<int> toErase;
+ int n = 0;
+ for(auto& p : particles)
+ {
+ auto& lifetime = p.Get<float>(ParticleStream::LIFETIME_STREAM_BIT);
+ lifetime -= (float(dt.count()) / 1000.0f);
+ if(lifetime <= 0.0f)
+ {
+ toErase.emplace_back(n);
+ }
+ ++n;
+ }
+
+ if(!toErase.empty())
+ {
+ int indexShift = 0;
+ for(auto& v : toErase)
+ {
+ GetImplementation(mParticleList).ReleaseParticle(v - indexShift);
+ ++indexShift;
+ }
+ }
+ }
+ mLastUpdateMs = ms;
+
+ // apply initial emission count
+ if(mSystemStarted)
+ {
+ emissionCount = mEmissionCountOnStart;
+ mSystemStarted = false;
+ }
+
+ // Update source if there are any particles to be emitted
+ if(emissionCount)
+ {
+ // Apply active particles limiter
+ if(mActiveParticlesLimit && mParticleList.GetActiveParticleCount() + emissionCount > mActiveParticlesLimit)
+ {
+ emissionCount = mActiveParticlesLimit - mParticleList.GetActiveParticleCount();
+ }
+ UpdateSource(emissionCount);
+ }
+
+ // Update modifier stack
+ for(auto& modifier : mModifiers)
+ {
+ if(modifier)
+ {
+ // Parallel processing must be enabled in order to use MT mode
+ bool mt = GetImplementation(modifier).GetUpdater().IsMultiThreaded() && mParallelProcessing;
+
+ if(!mt) // single-threaded, update all particles in one go
+ {
+ GetImplementation(modifier).Update(mParticleList, 0, mParticleList.GetActiveParticleCount());
+ }
+ else
+ {
+ UpdateModifierMT(modifier);
+ }
+ }
+ }
+
+ UpdateDomain();
+}
+
+void ParticleEmitter::AttachTo(Actor actor)
+{
+ mActor = std::move(actor);
+}
+
+Actor ParticleEmitter::GetActor() const
+{
+ return mActor;
+}
+
+void ParticleEmitter::UpdateSource(uint32_t count)
+{
+ GetImplementation(mParticleSource).Update(mParticleList, count);
+}
+
+void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier)
+{
+ auto& threadPool = GetThreadPool();
+ auto workerThreads = threadPool.GetWorkerCount();
+ auto activeCount = mParticleList.GetActiveParticleCount();
+
+ // at least 10 particles per worker thread (should be parametrized)
+ // If less, continue ST
+ if(activeCount < workerThreads * 10)
+ {
+ GetImplementation(modifier).Update(mParticleList, 0, activeCount);
+ return;
+ }
+
+ auto partial = mParticleList.GetActiveParticleCount() / workerThreads;
+
+ // make tasks
+ struct UpdateTask
+ {
+ UpdateTask(Internal::ParticleModifier& modifier, ParticleSystem::ParticleList& list, uint32_t first, uint32_t count)
+ : mModifier(modifier),
+ mList(list),
+ mFirst(first),
+ mCount(count)
+ {
+ }
+
+ Internal::ParticleModifier& mModifier;
+ ParticleSystem::ParticleList& mList;
+ uint32_t mFirst;
+ uint32_t mCount;
+
+ void Update()
+ {
+ mModifier.Update(mList, mFirst, mCount);
+ }
+ };
+
+ std::vector<UpdateTask> updateTasks;
+ updateTasks.reserve(workerThreads);
+ std::vector<Task> tasks;
+
+ for(auto i = 0u; i < workerThreads; ++i)
+ {
+ auto index = i * partial;
+ auto count = partial;
+ if(i == workerThreads - 1 && index + count < activeCount)
+ {
+ count = activeCount - index;
+ }
+
+ updateTasks.emplace_back(GetImplementation(modifier), mParticleList, index, count);
+ tasks.emplace_back([&task = updateTasks.back()](uint32_t n)
+ {
+ //printf("Updating modifier: %d\n", n);
+ task.Update(); });
+ }
+
+ auto future = threadPool.SubmitTasks(tasks, 0);
+ future->Wait();
+}
+
+void ParticleEmitter::UpdateDomain()
+{
+ // TODO
+}
+
+void ParticleEmitter::SetEmissionRate(uint32_t ratePerSecond)
+{
+ mEmissionRatePerSecond = ratePerSecond;
+}
+
+uint32_t ParticleEmitter::GetEmissionRate() const
+{
+ return mEmissionRatePerSecond;
+}
+
+void ParticleEmitter::EnableParallelProcessing(bool enabled)
+{
+ mParallelProcessing = enabled;
+}
+
+bool ParticleEmitter::IsParallelProcessingEnabled() const
+{
+ return mParallelProcessing;
+}
+
+void ParticleEmitter::SetInitialParticleCount(uint32_t count)
+{
+ mEmissionCountOnStart = count;
+}
+
+uint32_t ParticleEmitter::GetInitialParticleCount() const
+{
+ return mEmissionCountOnStart;
+}
+
+void ParticleEmitter::SetActiveParticlesLimit(uint32_t count)
+{
+ mActiveParticlesLimit = count;
+}
+
+uint32_t ParticleEmitter::GetActiveParticlesLimit() const
+{
+ return mActiveParticlesLimit;
+}
+
+ParticleSystem::ParticleEmitter::Status ParticleEmitter::GetStatus() const
+{
+ auto statusMask = SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT | SIMULATION_STOPPED_STATUS_BIT;
+ auto status = (mParticleStatusBits & statusMask);
+
+ if(status & SIMULATION_PAUSED_STATUS_BIT)
+ {
+ return ParticleSystem::ParticleEmitter::Status::PAUSED;
+ }
+ else if(status & SIMULATION_STOPPED_STATUS_BIT)
+ {
+ return ParticleSystem::ParticleEmitter::Status::STOPPED;
+ }
+ else if(status & SIMULATION_STARTED_STATUS_BIT)
+ {
+ return ParticleSystem::ParticleEmitter::Status::STARTED;
+ }
+ else
+ {
+ return !IsComplete() ? ParticleSystem::ParticleEmitter::Status::INCOMPLETE : ParticleSystem::ParticleEmitter::Status::READY;
+ }
+}
+
+ParticleEmitter::ParticleEmitter()
+{
+ // Necessary to be called to initialize internal ParticleList
+ SetParticleCount(DEFAULT_PARTICLE_COUNT);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+namespace Dali::Toolkit::ParticleSystem
+{
+Dali::ThreadPool& GetThreadPool()
+{
+ static std::unique_ptr<Dali::ThreadPool> gThreadPool{nullptr};
+ static std::once_flag onceFlag;
+
+ // Intialize thread pool if not there yet, make sure it happens once and it's synchronized!,
+ // 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>();
+ threadPool->Initialize(4u); });
+ }
+
+ return *gThreadPool;
+}
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_EMITTER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_EMITTER_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/adaptor-framework/timer.h>
+#include <dali/public-api/object/base-object.h>
+#include <chrono>
+#include <ctime>
+#include <memory>
+
+// For multithreading update
+#include <dali/devel-api/threading/thread-pool.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+#include <dali-toolkit/public-api/particle-system/particle-emitter.h>
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class FrameCallback;
+class ParticleEmitter : public Dali::BaseObject, public Dali::ConnectionTracker
+{
+public:
+ /**
+ * @brief Constructor
+ */
+ ParticleEmitter();
+
+ /**
+ * @brief Destructor
+ */
+ ~ParticleEmitter() override = default;
+
+ /**
+ * @brief Tests whether emitter is complete (ready for simulation)
+ *
+ * @return True if emitter is complete, false otherwise
+ */
+ [[nodiscard]] bool IsComplete() const
+ {
+ return (mParticleStatusBits & STATUS_COMPLETE_BITS) == STATUS_COMPLETE_BITS;
+ }
+
+ [[nodiscard]] ParticleSystem::ParticleSource GetSource() const;
+
+ void SetSource(const ParticleSystem::ParticleSource& source);
+
+ [[nodiscard]] ParticleSystem::ParticleDomain GetDomain() const;
+
+ void SetDomain(const ParticleSystem::ParticleDomain& domain);
+
+ [[nodiscard]] ParticleSystem::ParticleRenderer GetRenderer() const;
+
+ [[nodiscard]] ParticleSystem::ParticleModifier GetModifierAt(uint32_t index);
+
+ void RemoveModifierAt(uint32_t index);
+
+ void SetRenderer(const ParticleSystem::ParticleRenderer& renderer);
+
+ void SetParticleCount(uint32_t maxParticleCount);
+
+ ParticleSystem::ParticleList& GetParticleList();
+
+ uint32_t AddModifier(const ParticleSystem::ParticleModifier& modifier);
+
+ void AttachTo(Actor actor);
+
+ [[nodiscard]] Actor GetActor() const;
+
+ void Update();
+
+ void UpdateSource(uint32_t count);
+
+ void UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier);
+
+ void UpdateDomain();
+
+ void SetEmissionRate(uint32_t ratePerSecond);
+
+ [[nodiscard]] uint32_t GetEmissionRate() const;
+
+ void SetInitialParticleCount(uint32_t count);
+
+ [[nodiscard]] uint32_t GetInitialParticleCount() const;
+
+ void Start();
+
+ void Stop();
+
+ void EnableParallelProcessing(bool enabled);
+
+ [[nodiscard]] bool IsParallelProcessingEnabled() const;
+
+ void SetActiveParticlesLimit(uint32_t count);
+
+ [[nodiscard]] uint32_t GetActiveParticlesLimit() const;
+
+ [[nodiscard]] ParticleSystem::ParticleEmitter::Status GetStatus() const;
+
+ [[nodiscard]] std::chrono::milliseconds GetCurrentTimeMillis() const;
+
+ // All these bits must be set in order to consider emitter COMPLETE
+ const uint32_t SOURCE_SET_STATUS_BIT = 1 << 0;
+ const uint32_t RENDERER_SET_STATUS_BIT = 1 << 1;
+ const uint32_t DOMAIN_SET_STATUS_BIT = 1 << 2;
+
+ // 1. Only one of these flags can be set at a time
+ // 2. They are invalid as long as emitter is INCOMPLETE
+ const uint32_t SIMULATION_STARTED_STATUS_BIT = 1 << 3;
+ const uint32_t SIMULATION_PAUSED_STATUS_BIT = 1 << 4;
+ const uint32_t SIMULATION_STOPPED_STATUS_BIT = 1 << 5;
+
+ const uint32_t STATUS_COMPLETE_BITS = SOURCE_SET_STATUS_BIT | RENDERER_SET_STATUS_BIT | DOMAIN_SET_STATUS_BIT;
+
+ ParticleSystem::ParticleSource mParticleSource; ///< Current particle source object
+ ParticleSystem::ParticleDomain mParticleDomain; ///< Current particle domain object
+
+ uint8_t mParticleStatusBits{0u}; ///< Current status of the emitter
+
+ // List of particles
+ ParticleSystem::ParticleList mParticleList;
+
+ std::vector<ParticleSystem::ParticleModifier> mModifiers;
+
+ ParticleSystem::ParticleRenderer mParticleRenderer;
+
+ Actor mActor;
+
+ uint32_t mEmissionRatePerSecond{1u};
+ std::atomic<uint32_t> mEmissionCountOnStart{0u};
+ std::atomic<uint32_t> mActiveParticlesLimit{0u}; ///< 0 - unlimited
+ std::atomic<bool> mSystemStarted{false};
+ std::chrono::milliseconds mCurrentMilliseconds{0};
+ std::chrono::milliseconds mLastUpdateMs{0};
+
+ bool mParallelProcessing{false};
+ std::unique_ptr<FrameCallback> mFrameCallback;
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+// Returns thread pool shared by whole particle system
+Dali::ThreadPool& GetThreadPool();
+
+inline Internal::ParticleEmitter& GetImplementation(ParticleSystem::ParticleEmitter& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleEmitter handle is empty");
+
+ BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<Internal::ParticleEmitter&>(handle);
+}
+
+inline const Internal::ParticleEmitter& GetImplementation(const ParticleSystem::ParticleEmitter& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleEmitter handle is empty");
+
+ const BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<const Internal::ParticleEmitter&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_EMITTER_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/particle-system/particle-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+
+Particle::Particle(Internal::ParticleList& ownerList, uint32_t index)
+: mOwnerList(ownerList),
+ mIndex(index)
+{
+}
+
+void* Particle::Get(ParticleStreamTypeFlagBit streamBit)
+{
+ auto streamIndex = mOwnerList.GetDefaultStreamIndex(streamBit);
+ 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));
+ return reinterpret_cast<uint8_t*>(ptr + (mIndex * dataSize));
+}
+
+uint32_t Particle::GetIndex() const
+{
+ return mIndex;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/math/quaternion.h>
+#include <dali/public-api/math/vector3.h>
+#include <dali/public-api/object/base-object.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class Particle : public Dali::BaseObject
+{
+public:
+ ~Particle() = default;
+
+ Particle(Internal::ParticleList& ownerList, uint32_t index);
+
+ void* Get(ParticleStreamTypeFlagBit streamBit);
+
+ void* GetByIndex(uint32_t streamIndex);
+
+ uint32_t GetIndex() const;
+
+private:
+ Internal::ParticleList& mOwnerList;
+ uint32_t mIndex;
+};
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::Particle& GetImplementation(ParticleSystem::Particle& particle)
+{
+ DALI_ASSERT_ALWAYS(particle && "Particle handle is empty");
+
+ BaseObject& handle = particle.GetBaseObject();
+
+ return static_cast<Internal::Particle&>(handle);
+}
+
+inline const Internal::Particle& GetImplementation(const ParticleSystem::Particle& particle)
+{
+ DALI_ASSERT_ALWAYS(particle && "Particle handle is empty");
+
+ const BaseObject& handle = particle.GetBaseObject();
+
+ return static_cast<const Internal::Particle&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/common/vector-wrapper.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector3>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT3;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector4>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT4;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector2>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT2;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<float>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT;
+}
+
+ParticleList::ParticleList(uint32_t capacity, ParticleSystem::ParticleList::ParticleStreamTypeFlags streamFlags)
+{
+ // capacity makes for max immutable particle count
+ mMaxParticleCount = capacity;
+
+ // 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;
+ }
+ if(streamFlags & ParticleStream::ROTATION_STREAM_BIT)
+ {
+ AddStream(Vector4::ZERO, "aStreamRotation", false);
+ mBuiltInStreamMap[uint32_t(ParticleStream::ROTATION_STREAM_BIT)] = mDataStreams.size() - 1;
+ }
+ if(streamFlags & ParticleStream::SCALE_STREAM_BIT)
+ {
+ AddStream(Vector3::ONE, "aStreamScale", false);
+ mBuiltInStreamMap[uint32_t(ParticleStream::SCALE_STREAM_BIT)] = mDataStreams.size() - 1;
+ }
+ if(streamFlags & ParticleStream::VELOCITY_STREAM_BIT)
+ {
+ AddStream(Vector3::ZERO, "aStreamVelocity", false);
+ mBuiltInStreamMap[uint32_t(ParticleStream::VELOCITY_STREAM_BIT)] = mDataStreams.size() - 1;
+ }
+ if(streamFlags & ParticleStream::COLOR_STREAM_BIT)
+ {
+ AddStream(Color::YELLOW, "aStreamColor", false);
+ mBuiltInStreamMap[uint32_t(ParticleStream::COLOR_STREAM_BIT)] = mDataStreams.size() - 1;
+ }
+ if(streamFlags & ParticleStream::OPACITY_STREAM_BIT)
+ {
+ AddStream(0.0f, "aStreamOpacity", false);
+ mBuiltInStreamMap[uint32_t(ParticleStream::OPACITY_STREAM_BIT)] = mDataStreams.size() - 1;
+ }
+ if(streamFlags & ParticleStream::LIFETIME_STREAM_BIT)
+ {
+ AddStream(0.0f, "aStreamLifetime", false);
+ mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_STREAM_BIT)] = mDataStreams.size() - 1;
+ }
+
+ // create free chain
+ mFreeChain.resize(capacity);
+ for(auto i = 0u; i < mFreeChain.size(); ++i)
+ {
+ mFreeChain[i] = i + 1;
+ }
+ mFreeChain[mFreeChain.size() - 1] = 0;
+ mFreeIndex = 0;
+}
+
+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);
+
+ // Update element size
+ mParticleStreamElementSize = 0;
+ mParticleStreamElementSizeWithLocal = 0;
+ for(auto& ds : mDataStreams)
+ {
+ if(!ds->localStream)
+ {
+ mParticleStreamElementSize += ds->dataSize;
+ }
+ mParticleStreamElementSizeWithLocal += ds->dataSize;
+ }
+
+ return mDataStreams.size() - 1;
+}
+
+void* ParticleList::GetRawStream(uint32_t index)
+{
+ if(index < mDataStreams.size() && mDataStreams[index])
+ {
+ return mDataStreams[index]->data.data();
+ }
+ return nullptr;
+}
+
+uint32_t ParticleList::GetStreamCount() const
+{
+ return mDataStreams.size();
+}
+
+uint32_t ParticleList::GetParticleCount() const
+{
+ return mMaxParticleCount;
+}
+
+uint32_t ParticleList::GetActiveParticleCount() const
+{
+ return mParticles.size();
+}
+
+ParticleStream::StreamDataType ParticleList::GetStreamDataType(uint32_t streamIndex)
+{
+ return mDataStreams[streamIndex]->type;
+}
+
+const std::string& ParticleList::GetStreamName(uint32_t streamIndex) const
+{
+ return mDataStreams[streamIndex]->streamName;
+}
+
+bool ParticleList::IsStreamLocal(uint32_t streamIndex) const
+{
+ return mDataStreams[streamIndex]->localStream;
+}
+
+uint32_t ParticleList::GetStreamDataTypeSize(uint32_t streamIndex) const
+{
+ return mDataStreams[streamIndex]->dataSize;
+}
+
+ParticleSystem::Particle ParticleList::NewParticle(float lifetime)
+{
+ if(mParticles.size() < mMaxParticleCount)
+ {
+ auto newIndex = int32_t(mFreeIndex);
+ mFreeIndex = int32_t(mFreeChain[mFreeIndex]);
+ mAliveParticleCount++;
+
+ // Add particle
+ mParticles.emplace_back(new Internal::Particle(*this, newIndex));
+
+ // Set particle lifetime
+ auto& particle = mParticles.back();
+
+ particle.Get<float>(ParticleStream::LIFETIME_STREAM_BIT) = lifetime;
+
+ return mParticles.back();
+ }
+ return {nullptr};
+}
+
+uint32_t ParticleList::GetStreamElementSize(bool includeLocalStream)
+{
+ if(includeLocalStream)
+ {
+ return mParticleStreamElementSizeWithLocal;
+ }
+ else
+ {
+ return mParticleStreamElementSize;
+ }
+}
+
+void ParticleList::ReleaseParticle(uint32_t particleIndex)
+{
+ auto it = mParticles.begin();
+ std::advance(it, particleIndex);
+
+ // Point at this slot of memory as next free slot
+ auto& p = *it;
+ if(mFreeIndex)
+ {
+ mFreeChain[p.GetIndex()] = mFreeIndex;
+ mFreeIndex = p.GetIndex();
+ }
+
+ // Remove particle from the list
+ mParticles.erase(it);
+ mAliveParticleCount--;
+}
+
+void* ParticleList::GetDefaultStream(ParticleStreamTypeFlagBit streamBit)
+{
+ return GetRawStream(mBuiltInStreamMap[streamBit]);
+}
+
+uint32_t ParticleList::GetDefaultStreamIndex(ParticleStreamTypeFlagBit streamBit)
+{
+ return mBuiltInStreamMap[uint32_t(streamBit)];
+}
+
+std::list<ParticleSystem::Particle>& ParticleList::GetParticles()
+{
+ return mParticles;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
--- /dev/null
+#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.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+#include <dali-toolkit/public-api/particle-system/particle.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-object.h>
+#include <algorithm>
+#include <list>
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+template<class T>
+struct StreamDataTypeWrapper
+{
+ static ParticleStream::StreamDataType GetType()
+ {
+ return {};
+ }
+};
+
+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)
+ {
+ this->capacity = capacity;
+ data.resize(capacity * dataSize);
+ if(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);
+ }
+ }
+ type = dataType;
+ alive = 0u;
+ this->dataSize = dataSize;
+ }
+
+ void SetStreamName(const char* name)
+ {
+ streamName = name;
+ }
+
+ void SetStreamLocal(bool local)
+ {
+ localStream = local;
+ }
+
+ /**
+ * Converts raw data into requested type (does not guarantee compatibility)
+ */
+ template<class T>
+ T* GetAs()
+ {
+ return reinterpret_cast<T*>(data.data());
+ }
+
+ ParticleStream::StreamDataType type;
+ std::vector<uint8_t> data;
+ std::string streamName;
+ uint32_t alive{0u};
+ uint32_t capacity;
+ uint32_t dataSize;
+ bool localStream{true};
+};
+
+/**
+ * Particle list stores particle-specific data and manages the particles memory
+ * It can return a sub-list.
+ *
+ * ParticleList manages the storage memory.
+ *
+ *
+ */
+class ParticleList : public Dali::BaseObject
+{
+public:
+
+ ParticleList(uint32_t capacity, ParticleSystem::ParticleList::ParticleStreamTypeFlags streamFlags);
+
+ ~ParticleList();
+
+ /**
+ * Returns raw pointer to the stream data
+ */
+ void* GetRawStream(uint32_t index);
+
+ /**
+ * Returns number of available data streams
+ * @return
+ */
+ uint32_t GetStreamCount() const;
+
+ /**
+ * Returns number of particles per list
+ * @return
+ */
+ uint32_t GetParticleCount() const;
+
+ /**
+ * Returns number of currently active particles
+ * @return
+ */
+ uint32_t GetActiveParticleCount() const;
+
+ /**
+ * Returns stream data-type
+ * @param streamIndex
+ * @return
+ */
+ ParticleStream::StreamDataType GetStreamDataType(uint32_t streamIndex);
+
+ /**
+ * Returns stream data type size
+ * @param streamIndex
+ * @return
+ */
+ [[nodiscard]] uint32_t GetStreamDataTypeSize(uint32_t streamIndex) const;
+
+ [[nodiscard]] const std::string& GetStreamName(uint32_t streamIndex) const;
+
+ [[nodiscard]] bool IsStreamLocal(uint32_t streamIndex) const;
+
+ /**
+ * Allocates new particle in the streams
+ * @param lifetime
+ * @return
+ */
+ ParticleSystem::Particle NewParticle(float lifetime);
+
+ void* GetDefaultStream(ParticleStreamTypeFlagBit streamBit);
+
+ uint32_t GetDefaultStreamIndex(ParticleStreamTypeFlagBit streamBit);
+
+ std::list<ParticleSystem::Particle>& GetParticles();
+
+ void ReleaseParticle(uint32_t particleIndex);
+
+ uint32_t GetStreamElementSize(bool includeLocalStream);
+
+private:
+ template<class T>
+ uint32_t AddStream(const T& defaultValue, const char* streamName, bool localStream)
+ {
+ return AddStream(sizeof(T), &defaultValue, StreamDataTypeWrapper<T>::GetType(), streamName, localStream);
+ }
+
+public:
+ /**
+ * Adds new stream and returns index
+ */
+ uint32_t AddStream(uint32_t sizeOfDataType, const void* defaultValue, ParticleStream::StreamDataType dataType, const char* streamName, bool localStream);
+
+private:
+ std::vector<char> mBuffer[2];
+
+ uint32_t mAliveParticleCount{0u};
+ uint32_t mMaxParticleCount;
+
+ // Data storage
+ std::vector<std::unique_ptr<ParticleDataStream>> mDataStreams;
+
+ std::vector<uint32_t> mFreeChain;
+ int32_t mFreeIndex{0u};
+
+ std::map<uint32_t, uint32_t> mBuiltInStreamMap;
+
+ std::list<ParticleSystem::Particle> mParticles;
+
+ uint32_t mParticleStreamElementSizeWithLocal{0u};
+ uint32_t mParticleStreamElementSize{0u};
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleList& GetImplementation(ParticleSystem::ParticleList& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleList handle is empty");
+
+ BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<Internal::ParticleList&>(handle);
+}
+
+inline const Internal::ParticleList& GetImplementation(const ParticleSystem::ParticleList& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleList handle is empty");
+
+ const BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<const Internal::ParticleList&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_LIST_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-modifier-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+ParticleModifier::ParticleModifier(std::unique_ptr<ParticleModifierInterface>&& updater)
+{
+ mUpdater = std::move(updater);
+}
+
+void ParticleModifier::Update(ParticleSystem::ParticleList& list, uint32_t first, uint32_t count)
+{
+ mUpdater->Update(list, first, count);
+}
+
+ParticleModifierInterface& ParticleModifier::GetUpdater()
+{
+ return *mUpdater;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_MODIFIER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_MODIFIER_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+#include <dali/public-api/object/base-object.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleList;
+class ParticleEmitter;
+class ParticleModifier : public Dali::BaseObject
+{
+public:
+ ParticleModifier(std::unique_ptr<ParticleModifierInterface>&& updater);
+
+ void Update(ParticleSystem::ParticleList& list, uint32_t first, uint32_t count);
+
+ ParticleModifierInterface& GetUpdater();
+
+private:
+ std::unique_ptr<ParticleModifierInterface> mUpdater;
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleModifier& GetImplementation(ParticleSystem::ParticleModifier& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleModifier handle is empty");
+
+ BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<Internal::ParticleModifier&>(handle);
+}
+
+inline const Internal::ParticleModifier& GetImplementation(const ParticleSystem::ParticleModifier& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleModifier handle is empty");
+
+ const BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<const Internal::ParticleModifier&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_MODIFIER_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
+#include <dali/devel-api/rendering/renderer-devel.h>
+
+#include <dali/devel-api/actors/actor-devel.h>
+#include <dali/devel-api/common/capabilities.h>
+#include <dali/graphics-api/graphics-buffer.h>
+#include <dali/graphics-api/graphics-controller.h>
+#include <dali/graphics-api/graphics-program.h>
+#include <dali/graphics-api/graphics-shader.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+ParticleRenderer::ParticleRenderer()
+{
+ mStreamBufferUpdateCallback = Dali::VertexBufferUpdateCallback::New(this, &ParticleRenderer::OnStreamBufferUpdate);
+}
+
+void ParticleRenderer::SetEmitter(ParticleSystem::Internal::ParticleEmitter* emitter)
+{
+ mEmitter = emitter;
+}
+
+void ParticleRenderer::SetTexture(const Dali::Texture& texture)
+{
+ mTexture = texture;
+}
+
+void ParticleRenderer::SetBlendingMode(BlendingMode blendingMode)
+{
+ mBlendingMode = blendingMode;
+}
+
+void ParticleRenderer::CreateShader()
+{
+ // Create shader dynamically
+ auto& list = GetImplementation(mEmitter->GetParticleList());
+ auto streamCount = list.GetStreamCount();
+
+ static const char* ATTR_GLSL_TYPES[] =
+ {
+ "float", "vec2", "vec3", "vec4", "int", "ivec2", "ivec3", "ivec4"};
+
+ static const Property::Type ATTR_TYPES[] =
+ {
+ Property::Type::FLOAT,
+ Property::Type::VECTOR2,
+ Property::Type::VECTOR3,
+ Property::Type::VECTOR4,
+ Property::Type::INTEGER,
+ Property::Type::VECTOR2, // This represents floats but by binary write it shouldn't matter (?)
+ Property::Type::VECTOR3,
+ Property::Type::VECTOR4,
+ };
+
+ struct Vertex2D
+ {
+ Vertex2D(const Vector2& _co, const Vector2& _uv)
+ : co(_co),
+ uv(_uv)
+ {
+ }
+ Dali::Vector2 co{};
+ Dali::Vector2 uv{};
+ };
+
+ uint32_t streamElementSize = 0u;
+ Property::Map streamAtttributes;
+
+ std::stringstream ss;
+ for(auto i = 0u; i < streamCount; ++i)
+ {
+ // Don't add local streams to the shader
+ if(!list.IsStreamLocal(i))
+ {
+ uint32_t dataTypeSize = list.GetStreamDataTypeSize(i);
+ auto dataTypeIndex = uint32_t(list.GetStreamDataType(i));
+ const auto& streamName = list.GetStreamName(i);
+ streamElementSize += dataTypeSize;
+ char key[256];
+ if(streamName.empty())
+ {
+ sprintf(key, "aStreamAttr_%d", i);
+ }
+ else
+ {
+ sprintf(key, "%s", streamName.c_str());
+ }
+ streamAtttributes.Add(key, ATTR_TYPES[dataTypeIndex]);
+
+ // Add shader attribute line
+ ss << "INPUT mediump " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n";
+ }
+ }
+
+ auto streamAttributesStr = ss.str();
+
+ /**
+ * - The MVP comes from the Actor that the particle renderer is attached to
+ * - Attributes are added dynamically based on the particle system properties
+ * - There are two buffers bound
+ * * Geometry buffer (in this instance, a quad)
+ * * ParticleSystem stream buffer with interleaved data
+ * - ParticleSystem buffer is being updated every frame
+ */
+ std::string vertexShaderCode = streamAttributesStr + std::string(
+
+ "INPUT mediump vec2 aPosition;\n\
+ INPUT mediump vec2 aTexCoords;\n\
+ \n\
+ uniform mediump mat4 uMvpMatrix;\n\
+ uniform mediump vec3 uSize;\n\
+ uniform lowp vec4 uColor;\n\
+ \
+ OUTPUT mediump 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 position = pos + vec4(aStreamPosition, 0.0);\n\
+ vTexCoord = aTexCoords;\n\
+ vColor = uColor * aStreamColor;\n\
+ gl_Position = uMvpMatrix * position ;\n\
+ }\n");
+
+ std::string fragmentShaderCode =
+ {
+ "INPUT mediump vec2 vTexCoord;\n\
+ INPUT mediump vec4 vColor;\n\
+ uniform sampler2D sTexture;\n\
+ \n\
+ void main()\n\
+ {\n\
+ lowp vec4 col = TEXTURE(sTexture, vTexCoord) * vColor;\n\
+ if(col.a < 0.1) { discard; }\
+ fragColor = col;\n\
+ }\n"};
+
+ mShader = Shader::New(Dali::Shader::GetVertexShaderPrefix() + vertexShaderCode, Dali::Shader::GetFragmentShaderPrefix() + fragmentShaderCode);
+ mGeometry = Geometry::New();
+
+ // Configure geometry attributes
+ Property::Map geometryMap;
+ geometryMap.Add("aPosition", Dali::Property::VECTOR2);
+ geometryMap.Add("aTexCoords", Dali::Property::VECTOR2);
+
+ // One vertex buffer with geometry
+ VertexBuffer vertexBuffer0 = VertexBuffer::New(geometryMap);
+
+ // fill the buffer entirely
+ // 2D quad
+ const static Vector2 C(0.5f, 0.5f);
+ struct Quad2D
+ {
+ Vertex2D a0{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
+ Vertex2D a1{Vector2(1.0f, 0.0f) - C, Vector2(1.0f, 0.0f)};
+ Vertex2D a2{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
+ Vertex2D a3{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
+ Vertex2D a4{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
+ Vertex2D a5{Vector2(0.0f, 1.0f) - C, Vector2(0.0f, 1.0f)};
+ } QUAD;
+
+ std::vector<Quad2D> quads;
+ quads.resize(mEmitter->GetParticleList().GetCapacity());
+ std::fill(quads.begin(), quads.end(), QUAD);
+ vertexBuffer0.SetData(quads.data(), 6u * quads.size());
+
+ // Second vertex buffer with stream data
+ VertexBuffer vertexBuffer1 = VertexBuffer::New(streamAtttributes);
+
+ /**
+ * For more efficient stream management we need to support glVertexAttribDivisor() function.
+ * This will allow step 1 attribute per 4 vertices (GLES3+). Problem: DALi doesn't support instancing
+ *
+ * For older GLES2 we need to duplicate stream data (4x more memory in case of using a quad geometry)
+ *
+ * Point-sprites may be of use in the future (problem: point sprites use screen space)
+ */
+
+ // Based on the particle system, populate buffer
+ mGeometry.AddVertexBuffer(vertexBuffer0);
+ mGeometry.AddVertexBuffer(vertexBuffer1);
+
+ mGeometry.SetType(Geometry::TRIANGLES);
+
+ mVertexBuffer = vertexBuffer0;
+ mStreamBuffer = vertexBuffer1;
+
+ // Set some initial data for streambuffer to force initialization
+ std::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
+
+ // Sets up callback
+ mStreamBuffer.SetVertexBufferUpdateCallback(std::move(mStreamBufferUpdateCallback));
+
+ mRenderer = Renderer::New(mGeometry, mShader);
+
+ mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::CONTINUOUSLY);
+ // If no texture created, the substitute rect 2x2 texture will be used
+ if(!mTexture)
+ {
+ mTexture = Texture::New(TextureType::TEXTURE_2D, Pixel::RGBA8888, 2u, 2u);
+ auto* pixelArray = new uint32_t[4]{
+ 0xFF0000FF, 0xFF0000FF, 0xFF0000FF, 0xFF0000FF};
+
+ auto pixelData = PixelData::New(reinterpret_cast<uint8_t*>(pixelArray), 16, 2, 2, Pixel::Format::RGBA8888, PixelData::DELETE_ARRAY);
+ mTexture.Upload(pixelData);
+ }
+
+ mTextureSet = TextureSet::New();
+ mTextureSet.SetTexture(0, mTexture);
+ // mTextureSet.SetSampler(0, Sampler());
+
+ mRenderer.SetTextures(mTextureSet);
+
+ // Attach renderer to the parent actor
+ mEmitter->GetActor().AddRenderer(mRenderer);
+
+ if(mBlendingMode == BlendingMode::SCREEN)
+ {
+ if(Dali::Capabilities::IsBlendEquationSupported(Dali::DevelBlendEquation::SCREEN))
+ {
+ mEmitter->GetActor().SetProperty(Dali::DevelActor::Property::BLEND_EQUATION, Dali::DevelBlendEquation::SCREEN);
+ }
+ else // Fallback to default
+ {
+ mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
+ }
+ }
+ else
+ {
+ mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
+ }
+}
+
+uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
+{
+ auto& list = GetImplementation(mEmitter->GetParticleList());
+
+ auto particleCount = list.GetActiveParticleCount(); // active particle count
+ auto particleMaxCount = list.GetParticleCount();
+ if(!particleCount)
+ {
+ return 0;
+ }
+
+ auto streamCount = list.GetStreamCount();
+
+ 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);
+ }
+ }
+
+ // Prepare source buffer (MUST BE OPTIMIZED TO AVOID ALLOCATING AND COPYING!!)
+ auto totalSize = particleMaxCount * elementSize * 6u;
+
+ // buffer sizes must match
+ if(totalSize != size)
+ {
+ // ASSERT here ?
+ return 0;
+ }
+
+ auto* dst = reinterpret_cast<uint8_t*>(streamData);
+
+ auto& particles = list.GetParticles();
+
+ // prepare worker threads
+ auto workerCount = GetThreadPool().GetWorkerCount();
+
+ // divide particles if over the threshold
+
+ [[maybe_unused]] bool runParallel = true;
+ if(!mEmitter->IsParallelProcessingEnabled() || particleCount < workerCount * 10) // don't run parallel if only a few particles to update
+ {
+ runParallel = false;
+ }
+ else
+ {
+ // Partial to handle
+ [[maybe_unused]] auto partialSize = (particleCount / workerCount);
+
+ struct UpdateTask
+ {
+ UpdateTask(Internal::ParticleRenderer& renderer, Internal::ParticleList& list, uint32_t particleStartIndex, uint32_t particleCount, void* basePtr)
+ : owner(renderer),
+ particleList(list),
+ startIndex(particleStartIndex),
+ count(particleCount),
+ ptr(reinterpret_cast<uint8_t*>(basePtr))
+ {
+ }
+
+ void Update()
+ {
+ // execute task
+ owner.UpdateParticlesTask(particleList, startIndex, count, ptr);
+ }
+
+ Internal::ParticleRenderer& owner;
+ Internal::ParticleList& particleList;
+ uint32_t startIndex;
+ uint32_t count;
+ uint8_t* ptr;
+ };
+
+ std::vector<UpdateTask> tasks;
+ tasks.reserve(workerCount);
+ std::vector<Task> taskQueue;
+ auto count = partialSize;
+
+ for(auto i = 0u; i < workerCount; ++i)
+ {
+ auto index = i * partialSize;
+ count = partialSize;
+
+ // 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(); });
+ }
+
+ // Execute worker tasks
+ auto future = GetThreadPool().SubmitTasks(taskQueue, 0);
+ // wait to finish
+ future->Wait();
+ }
+
+ // 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;
+ }
+ }
+ return particleCount * 6u; // return number of elements to render
+}
+
+Renderer ParticleRenderer::GetRenderer() const
+{
+ return mRenderer;
+}
+
+void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list,
+ uint32_t particleStartIndex,
+ uint32_t particleCount,
+ uint8_t* basePtr)
+{
+ auto& particles = list.GetParticles();
+ auto streamCount = list.GetStreamCount();
+ auto elementSize = list.GetStreamElementSize(false);
+
+ // calculate begin of buffer
+ uint8_t* dst = (basePtr + (elementSize * 6u) * particleStartIndex);
+
+ auto it = particles.begin();
+ std::advance(it, particleStartIndex);
+
+ for(; particleCount; particleCount--, it++)
+ {
+ ParticleSystem::Particle& p = *it;
+ // 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;
+ }
+}
+
+bool ParticleRenderer::Initialize()
+{
+ if(!mInitialized)
+ {
+ CreateShader();
+ mInitialized = true;
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_RENDERER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_RENDERER_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <memory>
+
+#include <dali-toolkit/public-api/particle-system/particle-emitter.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+#include <dali-toolkit/public-api/particle-system/particle.h>
+
+#include <dali/public-api/actors/actor.h>
+#include <dali/public-api/object/base-object.h>
+#include <dali/public-api/rendering/renderer.h>
+#include <dali/public-api/rendering/vertex-buffer.h>
+#include <dali/public-api/signals/render-callback.h>
+
+namespace Dali::Graphics
+{
+class Controller;
+class Shader;
+} // namespace Dali::Graphics
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleEmitter;
+}
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleEmitter;
+class ParticleRenderer;
+class ParticleList;
+
+class ParticleRenderer : public Dali::BaseObject
+{
+public:
+ ParticleRenderer();
+
+ /**
+ * Create shader
+ */
+ void CreateShader();
+
+ void SetTexture(const Dali::Texture& texture);
+
+ void SetBlendingMode(BlendingMode blendingMode);
+
+ bool Initialize();
+
+ void SetEmitter(ParticleSystem::Internal::ParticleEmitter* emitter);
+
+ void UpdateParticlesTask(Internal::ParticleList& list,
+ uint32_t particleStartIndex,
+ uint32_t particleCount,
+ uint8_t* dst);
+
+ [[nodiscard]] Renderer GetRenderer() const;
+
+ uint32_t OnStreamBufferUpdate(void* data, size_t size);
+
+ bool mUsingStreamDivisor{true}; ///< If attribute divisor is supported, it's going to be used
+
+ Internal::ParticleEmitter* mEmitter{nullptr}; ///< Emitter implementation that uses the renderer
+
+ Dali::Texture mTexture;
+ Dali::TextureSet mTextureSet;
+ Dali::Renderer mRenderer;
+ Dali::Shader mShader;
+ Dali::Geometry mGeometry;
+ Dali::VertexBuffer mVertexBuffer;
+ Dali::VertexBuffer mStreamBuffer;
+ BlendingMode mBlendingMode;
+
+ std::unique_ptr<Dali::VertexBufferUpdateCallback> mStreamBufferUpdateCallback;
+
+ bool mInitialized{false};
+};
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+namespace Dali
+{
+
+inline Toolkit::ParticleSystem::Internal::ParticleRenderer& GetImplementation(Toolkit::ParticleSystem::ParticleRenderer& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleRenderer handle is empty");
+
+ BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<Toolkit::ParticleSystem::Internal::ParticleRenderer&>(handle);
+}
+
+inline const Toolkit::ParticleSystem::Internal::ParticleRenderer& GetImplementation(const Toolkit::ParticleSystem::ParticleRenderer& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleRenderer handle is empty");
+
+ const BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<const Toolkit::ParticleSystem::Internal::ParticleRenderer&>(handle);
+}
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_RENDERER_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/internal/particle-system/particle-source-impl.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+ParticleSource::ParticleSource(std::unique_ptr<ParticleSourceInterface>&& sourceUpdater)
+: mUpdater(std::move(sourceUpdater))
+{
+}
+
+void ParticleSource::Update(ParticleSystem::ParticleList& list, uint32_t count)
+{
+ // updating the source
+ mUpdater->Update(list, count);
+}
+
+ParticleSourceInterface& ParticleSource::GetUpdater() const
+{
+ return *mUpdater;
+}
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_SOURCE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_SOURCE_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+#include <dali/public-api/object/base-object.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleList;
+class ParticleEmitter;
+class ParticleSource : public Dali::BaseObject
+{
+public:
+ ~ParticleSource() override = default;
+
+ explicit ParticleSource(std::unique_ptr<ParticleSourceInterface>&& sourceUpdater);
+
+ void Update(ParticleSystem::ParticleList& list, uint32_t count);
+
+ [[nodiscard]] ParticleSourceInterface& GetUpdater() const;
+
+private:
+ std::unique_ptr<ParticleSourceInterface> mUpdater;
+};
+
+} // namespace Dali::Toolkit::ParticleSystem::Internal
+
+namespace Dali::Toolkit::ParticleSystem
+{
+inline Internal::ParticleSource& GetImplementation(ParticleSystem::ParticleSource& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleSource handle is empty");
+
+ BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<Internal::ParticleSource&>(handle);
+}
+
+inline const Internal::ParticleSource& GetImplementation(const ParticleSystem::ParticleSource& source)
+{
+ DALI_ASSERT_ALWAYS(source && "ParticleSource handle is empty");
+
+ const BaseObject& handle = source.GetBaseObject();
+
+ return static_cast<const Internal::ParticleSource&>(handle);
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_SOURCE_H
\ No newline at end of file
${public_api_src_dir}/image-loader/image-url.cpp
${public_api_src_dir}/image-loader/async-image-loader.cpp
${public_api_src_dir}/image-loader/sync-image-loader.cpp
+ ${public_api_src_dir}/particle-system/particle.cpp
+ ${public_api_src_dir}/particle-system/particle-domain.cpp
+ ${public_api_src_dir}/particle-system/particle-emitter.cpp
+ ${public_api_src_dir}/particle-system/particle-list.cpp
+ ${public_api_src_dir}/particle-system/particle-modifier.cpp
+ ${public_api_src_dir}/particle-system/particle-renderer.cpp
+ ${public_api_src_dir}/particle-system/particle-source.cpp
${public_api_src_dir}/styling/style-manager.cpp
${public_api_src_dir}/transition/fade-transition.cpp
${public_api_src_dir}/transition/slide-transition.cpp
${public_api_src_dir}/visuals/text-visual-properties.h
)
+SET( public_api_particle_system_header_files
+ ${public_api_src_dir}/particle-system/particle-domain.h
+ ${public_api_src_dir}/particle-system/particle-emitter.h
+ ${public_api_src_dir}/particle-system/particle.h
+ ${public_api_src_dir}/particle-system/particle-list.h
+ ${public_api_src_dir}/particle-system/particle-modifier.h
+ ${public_api_src_dir}/particle-system/particle-renderer.h
+ ${public_api_src_dir}/particle-system/particle-source.h
+ ${public_api_src_dir}/particle-system/particle-types.h
+)
+
SET( public_api_transition_header_files
${public_api_src_dir}/transition/fade-transition.h
${public_api_src_dir}/transition/slide-transition.h
${public_api_visuals_header_files}
${public_api_camera_view_header_files}
${public_api_gl_view_header_files}
+ ${public_api_particle_system_header_files}
)
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/public-api/particle-system/particle-domain.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-domain-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+ParticleDomain ParticleDomain::New()
+{
+ return {new Internal::ParticleDomain()};
+}
+
+ParticleDomain::ParticleDomain(Internal::ParticleDomain* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleDomain ParticleDomain::DownCast(BaseHandle handle)
+{
+ return {dynamic_cast<Internal::ParticleDomain*>(handle.GetObjectPtr())};
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_DOMAIN_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_DOMAIN_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleDomain;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+/**
+ * @class ParticleDomain
+ *
+ * @brief ParticleDomain bounds simulation area to the specified region
+ */
+class DALI_TOOLKIT_API ParticleDomain : public Dali::BaseHandle
+{
+public:
+ /**
+ * @brief Constructor
+ */
+ ParticleDomain() = default;
+
+ /**
+ * @brief Creates new ParticleDomain object
+ *
+ * @return New ParticleDomain object
+ */
+ static ParticleDomain New();
+
+ /**
+ * @brief Downcasts a handle to ParticleDomain handle.
+ *
+ * If handle points to an ParticleDomain object, the downcast produces valid handle.
+ * If not, the returned handle is left uninitialized.
+ *
+ * @param[in] handle to An object
+ * @return handle to a ParticleDomain object or an uninitialized handle
+ */
+ static ParticleDomain DownCast(BaseHandle handle);
+
+private:
+ /// @cond internal
+ /**
+ * @brief This constructor is used by ParticleDomain::New() methods.
+ *
+ * @param [in] impl A pointer to a newly allocated implementation
+ */
+ ParticleDomain(Internal::ParticleDomain* impl);
+ /// @endcond
+};
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_DOMAIN_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/public-api/particle-system/particle-emitter.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+#include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
+
+// EXTERNAL INCLUDES
+#include <utility>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+ParticleEmitter ParticleEmitter::New()
+{
+ return {new Dali::Toolkit::ParticleSystem::Internal::ParticleEmitter()};
+}
+
+ParticleEmitter ParticleEmitter::DownCast(BaseHandle handle)
+{
+ return {dynamic_cast<Internal::ParticleEmitter*>(handle.GetObjectPtr())};
+}
+
+void ParticleEmitter::SetSource(const ParticleSource& particleSource)
+{
+ GetImplementation(*this).SetSource(particleSource);
+}
+
+ParticleSource ParticleEmitter::GetSource() const
+{
+ return GetImplementation(*this).GetSource();
+}
+
+void ParticleEmitter::SetDomain(const ParticleDomain& particleDomain)
+{
+ GetImplementation(*this).SetDomain(particleDomain);
+}
+
+void ParticleEmitter::SetRenderer(const ParticleRenderer& particleRenderer)
+{
+ GetImplementation(*this).SetRenderer(particleRenderer);
+}
+
+void ParticleEmitter::SetParticleCount(uint32_t maxParticleCount)
+{
+ GetImplementation(*this).SetParticleCount(maxParticleCount);
+}
+
+ParticleDomain ParticleEmitter::GetDomain() const
+{
+ return GetImplementation(*this).GetDomain();
+}
+
+ParticleRenderer ParticleEmitter::GetRenderer() const
+{
+ return GetImplementation(*this).GetRenderer();
+}
+
+uint32_t ParticleEmitter::AddModifier(const ParticleModifier& particleModifier)
+{
+ return GetImplementation(*this).AddModifier(particleModifier);
+}
+
+ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index)
+{
+ return GetImplementation(*this).GetModifierAt(index);
+}
+
+void ParticleEmitter::RemoveModifierAt(uint32_t index)
+{
+ return GetImplementation(*this).RemoveModifierAt(index);
+}
+
+void ParticleEmitter::AttachTo(Actor actor)
+{
+ GetImplementation(*this).AttachTo(std::move(actor));
+}
+
+ParticleEmitter::ParticleEmitter(Internal::ParticleEmitter* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleEmitter::Status ParticleEmitter::GetStatus() const
+{
+ return GetImplementation(*this).GetStatus();
+}
+
+ParticleList& ParticleEmitter::GetParticleList()
+{
+ return GetImplementation(*this).GetParticleList();
+}
+
+void ParticleEmitter::Start()
+{
+ GetImplementation(*this).Start();
+}
+
+void ParticleEmitter::Stop()
+{
+ GetImplementation(*this).Stop();
+}
+
+void ParticleEmitter::SetEmissionRate(uint32_t ratePerSecond)
+{
+ GetImplementation(*this).SetEmissionRate(ratePerSecond);
+}
+
+uint32_t ParticleEmitter::GetEmissionRate() const
+{
+ return GetImplementation(*this).GetEmissionRate();
+}
+
+[[maybe_unused]] void ParticleEmitter::EnableParallelProcessing(bool enabled)
+{
+ GetImplementation(*this).EnableParallelProcessing(enabled);
+}
+
+bool ParticleEmitter::IsParallelProcessingEnabled() const
+{
+ return GetImplementation(*this).IsParallelProcessingEnabled();
+}
+
+void ParticleEmitter::SetInitialParticleCount(uint32_t count)
+{
+ GetImplementation(*this).SetInitialParticleCount(count);
+}
+
+uint32_t ParticleEmitter::GetInitialParticleCount() const
+{
+ return GetImplementation(*this).GetInitialParticleCount();
+}
+
+void ParticleEmitter::SetActiveParticlesLimit(uint32_t count)
+{
+ GetImplementation(*this).SetActiveParticlesLimit(count);
+}
+
+uint32_t ParticleEmitter::GetActiveParticlesLimit() const
+{
+ return GetImplementation(*this).GetActiveParticlesLimit();
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_EMITTER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_EMITTER_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/actors/actor.h>
+#include <dali/public-api/object/base-handle.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleEmitter;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleSource;
+
+class ParticleDomain;
+
+class ParticleList;
+
+class ParticleModifier;
+
+class ParticleRenderer;
+
+/**
+ * @class ParticleEmitter
+ *
+ * ParticleEmitter manages a particle emission process. The primary function
+ * of the particle emitter class is to emit particles into a simulated environment.
+ *
+ * ParticleEmitter is responsible for:
+
+ * - Particle generation:
+ *
+ * The emitter generates particles with specific initial properties,
+ * such as position, velocity, size etc. It can create particles various ways which can be
+ * implemented by overriding ParticleSourceInterface. It may create particles in bursts, streams
+ * or in response to specific events. The ParticleSource must be set in order to generate particles
+ * in the system.
+ *
+ * - Particle simulation:
+ *
+ * The emitter updates state of each particle by invoking ParticleModifier stack. It updates particles
+ * over time to simulate desired behaviour. Particle modifiers may apply modifications to the system like
+ * applying forces (gravity, wind), integrating physics.
+ *
+ * The stack of modifiers is executed in order and output of previous modifier is an input of
+ * next one.
+ *
+ * At least one ParticleModifier must be set in order to update particle system and run
+ * the simulation.
+ *
+ * - Particle rendering
+ *
+ * ParticleRenderer must be set in order to render particles. The basic renderer renders only 2D billboard
+ * projected (always facing the camera) particles however the behaviour can be altered in order to render
+ * more complex systems.
+ *
+ * Rendering may be optimize for different graphics APIs.
+ *
+ * - Particle management
+ *
+ * The emitter manages the lifecycle of particles, including creation (via ParticleSource),
+ * update (via ParticleModifier stack) and removal (modifiers and specified lifetime of particles). It
+ * handles scenarios such as recycling particles that have reached the end of their lifespan, reusing them,
+ * or dynamically adjusting their properties based on the emitter's parameters or external factors.
+ *
+ * The particles are stored as ParticleList object which is generated internally. The emitter controls
+ * behaviour of the particle list.
+ *
+ * The basic components making the Particle System are:
+ * - ParticleEmitter - responsible for controlling the emission
+ * - ParticleSource - responsible for generating new particles in the system
+ * - ParticleModifier - responsible for altering the behaviour of particles (updates) and controlling the lifetime
+ * - ParticleRenderer - responsible for rendering the particle system
+ * - ParticleList - storage for particle data
+ * - Particle - view on a selected particle data
+ * - ParticleDomain - the domain of particle system (area/volume) that particle system is bound within
+ *
+ */
+class DALI_TOOLKIT_API ParticleEmitter : public Dali::BaseHandle
+{
+public:
+ /**
+ * @brief Enum describing status of emitter
+ */
+ enum class Status
+ {
+ INCOMPLETE, ///< Status set when not all data is set in order to run simulation
+ READY, ///< Emitter ready (fully set up)
+ STARTED, ///< Emitter started
+ PAUSED, ///< Emitter paused
+ STOPPED ///< Emitter stopped
+ };
+
+ /**
+ * @brief Creates new ParticleEmitter
+ *
+ * Function creates new ParticleEmitter object with given initial specification.
+ *
+ * @return valid emitter object
+ */
+ static ParticleEmitter New();
+
+ /**
+ * @brief Downcasts a handle to ParticleEmitter handle.
+ *
+ * If handle points to an ParticleEmitter object, the downcast produces valid handle.
+ * If not, the returned handle is left uninitialized.
+ *
+ * @param[in] handle to An object
+ * @return handle to a ParticleEmitter object or an uninitialized handle
+ */
+ static ParticleEmitter DownCast(BaseHandle handle);
+
+ /**
+ * @brief Enables parallel processing on the CPU
+ *
+ * This flag gives a hint to attached ParticleSystem objects to use
+ * multiple threads if possible.
+ *
+ * Setting this hint may have no effect after the particle system started
+ * simulation. It should be set before calling Start() function.
+ *
+ * @param[in] enabled if True it will try to use MT
+ */
+ void EnableParallelProcessing(bool enabled);
+
+ /**
+ * @brief Tests whether parallel processing is enabled
+ *
+ * @return True if parallel processing mode is enabled.
+ */
+ [[nodiscard]] bool IsParallelProcessingEnabled() const;
+
+ /**
+ * @brief Sets emitter source
+ *
+ * ParticleSource represents objects responsible for emitting
+ * new particles as well as reusing expired particles.
+ *
+ * @param[in] particleSource valid particle emitter source object
+ *
+ */
+ void SetSource(const ParticleSource& particleSource);
+
+ /**
+ * @brief Returns currently used particle emitter source
+ *
+ * @return Handle to the ParticleSource object
+ */
+ [[nodiscard]] ParticleSource GetSource() const;
+
+ /**
+ * @brief Sets emitter domain
+ *
+ * ParticleDomain encloses the area of particle emission.
+ *
+ * @param[in] particleDomain valid particle emitter domain
+ */
+ void SetDomain(const ParticleDomain& particleDomain);
+
+ /**
+ * @brief Sets emitter domain
+ *
+ * ParticleRenderer provides implementation for rendering set of particles
+ *
+ * @param[in] particleRenderer a valid instance of ParticleRenderer
+ */
+ void SetRenderer(const ParticleRenderer& particleRenderer);
+
+ /**
+ * @brief Sets maximum particle count in the system
+ *
+ * This value is mutable but changing number of particles will
+ * force regenerating the whole system data!
+ *
+ * @param[in] maxParticleCount maximum number of particles in the system
+ */
+ void SetParticleCount(uint32_t maxParticleCount);
+
+ /**
+ * @brief Returns currently used particle emitter domain
+ *
+ * @return Handle to the ParticleDomain object
+ */
+ [[nodiscard]] ParticleDomain GetDomain() const;
+
+ /**
+ * @brief Returns currently used particle emitter renderer
+ *
+ * @return Handle to the ParticleRenderer object
+ */
+ [[nodiscard]] ParticleRenderer GetRenderer() const;
+
+
+ /**
+ * @brief Attaches particle system to an actor
+ *
+ * @param[in] actor actor to attach the particle system
+ */
+ void AttachTo(Actor actor);
+
+ /**
+ * @brief Adds new modifier
+ * @param[in] particleMmodifier valid ParticleModifier object
+ *
+ * @return Index into the modifier stack associated with added modifier
+ */
+ uint32_t AddModifier(const ParticleModifier& particleMmodifier);
+
+ /**
+ * @brief Sets particle emission rate per second
+ *
+ * @param[in] ratePerSecond maximum number of particles emitted per second
+ */
+ void SetEmissionRate(uint32_t ratePerSecond);
+
+ /**
+ * @brief Returns emission rate per second
+ *
+ * @return Value of emission rate
+ */
+ [[nodiscard]] uint32_t GetEmissionRate() const;
+
+ /**
+ * @brief number of particles to be emitted on start of the emitter
+ *
+ * @param[in] count Initial count of particles in the system
+ */
+ void SetInitialParticleCount(uint32_t count);
+
+ /**
+ * @brief returns number of particles being emitted on start of the emitter
+ *
+ * @return number of particles
+ */
+ [[nodiscard]] uint32_t GetInitialParticleCount() const;
+
+ /**
+ * @brief Sets maximum number of particles alive
+ *
+ * This function limits number of active particles in the system.
+ *
+ * If set to 0, there is no limit.
+ *
+ * If set to non-zero, if the system reaches limit, no new particles
+ * will spawn until some of them die.
+ *
+ * @param[in] count Limit of active particles
+ */
+ void SetActiveParticlesLimit(uint32_t count);
+
+ /**
+ * @brief Returns active particles limit
+ *
+ * @return Number of active particles system is limited to
+ */
+ [[nodiscard]] uint32_t GetActiveParticlesLimit() const;
+
+ /**
+ * @brief Returns modifier in stack
+ *
+ * @param[in] index Index of modifier
+ * @return Returns valid pointer to the modifier or nullptr
+ */
+ ParticleModifier GetModifierAt(uint32_t index);
+
+ /**
+ * @brief Removes modifier at specified index
+ *
+ * @param[in] index of modifier to be removed
+ */
+ void RemoveModifierAt(uint32_t index);
+
+ /**
+ * @brief Returns ParticleList object
+ *
+ * @return Valid reference to the ParticleList
+ */
+ ParticleList& GetParticleList();
+
+ /**
+ * @brief Starts emitting particles
+ */
+ void Start();
+
+ /**
+ * @brief Stops emitting particles
+ */
+ void Stop();
+
+ /**
+ * @brief Returns current emitter status
+ *
+ * @return Current emitter status
+ */
+ [[nodiscard]] Status GetStatus() const;
+
+ /**
+ * @brief Constructor
+ */
+ ParticleEmitter() = default;
+
+private:
+ /// @cond internal
+ /**
+ * @brief This constructor is used by ParticleEmitter::New() methods.
+ *
+ * @param [in] impl A pointer to a newly allocated implementation
+ */
+ ParticleEmitter(Dali::Toolkit::ParticleSystem::Internal::ParticleEmitter* impl);
+ /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_EMITTER_H
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-list-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-list.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector3>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT3;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector4>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT4;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<Vector2>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT2;
+}
+
+template<>
+ParticleStream::StreamDataType StreamDataTypeWrapper<float>::GetType()
+{
+ return ParticleStream::StreamDataType::FLOAT;
+}
+
+ParticleList ParticleList::New(uint32_t capacity, const ParticleStreamTypeFlags& defaultStreams)
+{
+ return {new ParticleSystem::Internal::ParticleList(capacity, defaultStreams)};
+}
+
+ParticleList ParticleList::DownCast(BaseHandle handle)
+{
+ return {dynamic_cast<Internal::ParticleList*>(handle.GetObjectPtr())};
+}
+
+uint32_t ParticleList::AddStream(void* defaults, size_t dataTypeSize, ParticleStream::StreamDataType dataType, bool localStream)
+{
+ return GetImplementation(*this).AddStream(dataTypeSize, defaults, dataType, "", localStream);
+}
+
+void* ParticleList::GetRawStream(uint32_t streamIndex)
+{
+ return GetImplementation(*this).GetRawStream(streamIndex);
+}
+
+void* ParticleList::GetDefaultStream(ParticleStreamTypeFlagBit streamBit)
+{
+ return GetImplementation(*this).GetDefaultStream(streamBit);
+}
+
+uint32_t ParticleList::GetActiveParticleCount()
+{
+ return GetImplementation(*this).GetActiveParticleCount();
+}
+
+ParticleList::ParticleList(Dali::Toolkit::ParticleSystem::Internal::ParticleList* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+uint32_t ParticleList::GetCapacity() const
+{
+ return GetImplementation(*this).GetParticleCount();
+}
+
+Particle ParticleList::NewParticle(float lifetime)
+{
+ return GetImplementation(*this).NewParticle(lifetime);
+}
+
+uint32_t ParticleList::GetParticleDataSize(bool includeLocalStreams)
+{
+ return GetImplementation(*this).GetStreamElementSize(includeLocalStreams);
+}
+
+std::list<Particle>& ParticleList::GetActiveParticles()
+{
+ return GetImplementation(*this).GetParticles();
+}
+
+ParticleList::ParticleList() = default;
+
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_LIST_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_LIST_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <cinttypes>
+#include <list>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleList;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+/**
+ * The function is a wrapper needed to retrieve data type enum through template
+ */
+template<class T>
+struct StreamDataTypeWrapper
+{
+ static ParticleStream::StreamDataType GetType()
+ {
+ return {};
+ }
+};
+
+/**
+ * @class ParticleList
+ *
+ * ParticleList defines a storage or a partial view on an existing storage of particle-related data.
+ *
+ * ParticleList contains streams of data (properties) laid out independently (non-interleaved).
+ *
+ * The layout is more optimal for:
+ * - parallel processing
+ * - data-oriented processing (CPU-cache friendly)
+ * - adding custom streams of data (for example, physics properties)
+ *
+ * Some streams are being added automatically by the emitter when certain modifiers are being added.
+ * If the modifier requires particular data it the emitter will update the list with proper stream.
+ *
+ * There are several built-in streams defined:
+ * - POSITION
+ * - VELOCITY
+ * - COLOR
+ * - OPACITY
+ * - SIZE
+ *
+ * The New() function allows adding streams upon creation.
+ *
+ * Custom streams may be used by custom renderers, modifiers and sources but also may play
+ * a role of temporary storage space when it comes to computing particle parameters.
+ *
+ * Represents all or subset of group of particles
+ */
+class DALI_TOOLKIT_API ParticleList : public Dali::BaseHandle
+{
+public:
+ /**
+ * @brief Constructor
+ */
+ ParticleList();
+
+ /**
+ * @brief Bit flags grouping built-in particle streams by type
+ */
+ struct ParticleStreamTypeFlags
+ {
+ ParticleStreamTypeFlags(const ParticleStreamTypeFlags& flags)
+ {
+ value = flags.value;
+ }
+
+ ParticleStreamTypeFlags(const ParticleStreamTypeFlagBit& bit)
+ : value(bit)
+ {
+ }
+
+ ParticleStreamTypeFlags& operator|=(ParticleStreamTypeFlagBit flagBit)
+ {
+ value |= flagBit;
+ return *this;
+ }
+
+ ParticleStreamTypeFlags operator&(ParticleStreamTypeFlagBit flagBit)
+ {
+ return (value & flagBit);
+ }
+
+ explicit operator uint32_t() const
+ {
+ return value;
+ }
+
+ explicit operator bool() const
+ {
+ return value;
+ }
+
+ uint32_t value;
+ };
+
+ /**
+ * @brief Creates new ParticleList
+ *
+ * ParticleList is a storage object that contains data for particle system
+ * layouted as an array of streams.
+ *
+ * Stream may contain data like position, velocity, etc.
+ *
+ * When new ParticleList is created the default streams can be pre-allocated.
+ *
+ * @param[in] capacity Maximum capacity (number of particles in the system)
+ * @param[in] defaultStreams Default data streams to pre-allocate
+ */
+ static ParticleList New(uint32_t capacity, const ParticleStreamTypeFlags& defaultStreams);
+
+ /**
+ * @brief Downcasts a handle to ParticleList handle.
+ *
+ * If handle points to an ParticleList object, the downcast produces valid handle.
+ * If not, the returned handle is left uninitialized.
+ *
+ * @param[in] handle to An object
+ * @return handle to a ParticleList object or an uninitialized handle
+ */
+ static ParticleList DownCast(BaseHandle handle);
+
+ /**
+ * @brief Registers new data-stream with given unique identifier
+ *
+ * @tparam T Type of data stored in the stream
+ * @param[in] defaults Default value of the stream uninitialized data
+ *
+ * Streams added using AddStream() function are automatically passed into
+ * the shader program as input attributes.
+ *
+ * If there is no need for the stream to be used as a shader attribute
+ * then use AddLocalStream() instead.
+ *
+ * @return Returns index of an allocated data stream
+ */
+ template<class T>
+ inline uint32_t AddStream(T defaults)
+ {
+ return AddStream(&defaults, StreamDataTypeWrapper<T>::GetType(), sizeof(T), false);
+ }
+
+ /**
+ * @brief Adds local data stream
+ *
+ * @tparam T Type of data stored in the stream
+ * @param[in] defaults Default value of the stream
+ *
+ * @return Returns index of newly allocated data stream
+ */
+ template<class T>
+ inline uint32_t AddLocalStream(T defaults)
+ {
+ return AddStream(&defaults, sizeof(T), StreamDataTypeWrapper<T>::GetType(), true);
+ }
+
+ template<class T>
+ T* GetStream(uint32_t streamIndex)
+ {
+ return reinterpret_cast<T*>(GetRawStream(streamIndex));
+ }
+
+ template<class T>
+ T* GetDefaultStream(ParticleStreamTypeFlagBit streamFlagBit)
+ {
+ return reinterpret_cast<T*>(GetDefaultStream(streamFlagBit));
+ }
+
+ /**
+ * @brief Returns size of list including only active particles
+ *
+ * @return
+ */
+ uint32_t GetActiveParticleCount();
+
+ /**
+ * @brief Returns capacity of particle list
+ */
+ [[nodiscard]] uint32_t GetCapacity() const;
+
+ /**
+ * Creates new particle in the list with specified lifetime
+ *
+ * @param[in] lifetime Expected lifetime of new particle (0.0f - lives forever)
+ * @return index into data streams
+ */
+ Particle NewParticle(float lifetime);
+
+ /**
+ * @brief Returns internal data size of streams
+ *
+ * @param[in] includeLocalStreams If true, the size will include local streams
+ * @return Size of data structure
+ */
+ uint32_t GetParticleDataSize(bool includeLocalStreams);
+
+ std::list<Particle>& GetActiveParticles();
+
+private:
+ /// @cond internal
+ /**
+ * @brief Adds new data stream to the list
+ *
+ * @param[in] defaults Default values to fill the stream with
+ * @param[in] dataTypeSize size of stored data type
+ * @param[in] dataType Stream data type
+ * @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);
+ /// @endcond
+
+ /// @cond internal
+ /**
+ * @brief Returns raw data pointer to the stream at given index
+ *
+ * @param[in] streamIndex Stream index
+ *
+ * @return Valid data pointer or nullptr if index invalid
+ */
+ void* GetRawStream(uint32_t streamIndex);
+ /// @endcond
+
+ /// @cond internal
+ /**
+ * @brief Returns raw data stream of a default (built-in) stream
+ * @param[in] streamBit Bit (ParticleStreamTypeFlagBit) representing the stream
+ *
+ * @return Returns valid pointer to the data or nullptr if stream hasn't been used
+ */
+ void* GetDefaultStream(ParticleStreamTypeFlagBit streamBit);
+ /// @endcond
+
+ /// @cond internal
+ /**
+ * @brief This constructor is used by ParticleList::New() methods.
+ *
+ * @param [in] impl A pointer to a newly allocated implementation
+ */
+ ParticleList(Dali::Toolkit::ParticleSystem::Internal::ParticleList* impl);
+ /// @endcond
+};
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <memory>
+
+#include <dali-toolkit/internal/particle-system/particle-modifier-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-modifier.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+ParticleModifier ParticleModifier::New(std::unique_ptr<ParticleModifierInterface>&& modifierUpdater)
+{
+ return {new Internal::ParticleModifier(std::move(modifierUpdater))};
+}
+
+ParticleModifier::ParticleModifier(Internal::ParticleModifier* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleModifier ParticleModifier::DownCast(BaseHandle handle)
+{
+ return {dynamic_cast<Internal::ParticleModifier*>(handle.GetObjectPtr())};
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_MODIFIER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_MODIFIER_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/signals/callback.h>
+#include <memory>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleModifier;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleEmitter;
+
+class ParticleList;
+
+class DALI_TOOLKIT_API ParticleModifierInterface
+{
+public:
+ /**
+ * @brief Destructor
+ */
+ virtual ~ParticleModifierInterface() = default;
+
+ /**
+ * @brief Update function to update the modifier to alter the behavior of particles.
+ * @param[in] particleList List of particles
+ * @param[in] firstParticleIndex Index of the first particle
+ * @param[in] particleCount Number of particles
+ */
+ virtual void Update(ParticleList& particleList, uint32_t firstParticleIndex, uint32_t particleCount) = 0;
+
+ /**
+ * @brief Will be called to check whether modifier supports multi-threading
+ *
+ * If modifier supports multi-threading then Update() function will be called
+ * providing partial range of particles to process.
+ *
+ * It is important to make sure, the batch of particles has no dependencies
+ * on particles from outside the batch. If this is the case, the function
+ * must return false and single threaded process will proceed.
+ *
+ * @return True if modifier supports multi-threading
+ */
+ virtual bool IsMultiThreaded()
+ {
+ return false;
+ }
+};
+
+/**
+ * @class ParticleModifier
+ *
+ * @brief ParticleModifer allows altering particle behaviour during simulation.
+ *
+ * Multiple modifiers can be used in the modifier stack.
+ *
+ * Output of previous modifier becomes an input of the next one.
+ *
+ */
+class DALI_TOOLKIT_API ParticleModifier : public Dali::BaseHandle
+{
+public:
+ /**
+ * @brief Constructor
+ */
+ ParticleModifier() = default;
+
+ /**
+ * @brief Destructor
+ */
+ ~ParticleModifier() = default;
+
+ /**
+ * @brief Creates new modifier with given functor object.
+ * The modifier takes over the ownership over the functor object.
+ *
+ * @param[in] modifierUpdater Functor for the modifier
+ * @return New ParticleModifier object
+ */
+ static ParticleModifier New(std::unique_ptr<ParticleModifierInterface>&& modifierUpdater);
+
+ /**
+ * @brief Creates new modifier with given class and construction arguments
+ *
+ * @return New ParticleModifier object
+ */
+ template<class T, class... Args>
+ static ParticleModifier New(Args... args)
+ {
+ return New(std::move(std::make_unique<T>(args...)));
+ }
+
+ /**
+ * @brief Downcasts a handle to ParticleModifier handle.
+ *
+ * If handle points to an ParticleModifier object, the downcast produces valid handle.
+ * If not, the returned handle is left uninitialized.
+ *
+ * @param[in] handle to An object
+ * @return handle to a ParticleModifier object or an uninitialized handle
+ */
+ static ParticleModifier DownCast(BaseHandle handle);
+
+private:
+ /// @cond internal
+ /**
+ * @brief This constructor is used by ParticleModifier::New() methods.
+ *
+ * @param [in] impl A pointer to a newly allocated implementation
+ */
+ ParticleModifier(Internal::ParticleModifier* impl);
+ /// @endcond
+};
+
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-renderer.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+ParticleRenderer ParticleRenderer::New()
+{
+ return {new Internal::ParticleRenderer()};
+}
+
+ParticleRenderer::ParticleRenderer(Internal::ParticleRenderer* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+void ParticleRenderer::SetTexture(const Dali::Texture& texture)
+{
+ GetImplementation(*this).SetTexture(texture);
+}
+
+void ParticleRenderer::SetBlendingMode(BlendingMode blendingMode)
+{
+ GetImplementation(*this).SetBlendingMode(blendingMode);
+}
+
+ParticleRenderer ParticleRenderer::DownCast(BaseHandle handle)
+{
+ return {dynamic_cast<Internal::ParticleRenderer*>(handle.GetObjectPtr())};
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_RENDERER_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_RENDERER_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/signals/callback.h>
+#include <memory>
+
+namespace Dali
+{
+class Texture;
+}
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleRenderer;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+/**
+ * @brief Blending modes for particle renderer
+ */
+enum class DALI_TOOLKIT_API BlendingMode
+{
+ ADDITIVE, ///< Additive blending mode (default)
+ DEFAULT = ADDITIVE,
+ SCREEN ///< SCREEN mode, Advanced blending support required
+};
+
+class DALI_TOOLKIT_API ParticleRenderer : public Dali::BaseHandle
+{
+public:
+ ParticleRenderer() = default;
+
+ /**
+ * @brief Creates new instance of basic 2D renderer
+ *
+ * @return Valid instance of renderer
+ */
+ static ParticleRenderer New();
+
+ /**
+ * @brief Sets blending mode for the renderer
+ *
+ * @param[in] blendingMode Valid particle blending mode to be used
+ */
+ void SetBlendingMode(BlendingMode blendingMode);
+
+ /**
+ * @brief Sets renderable as a 2D texture (sprites)
+ *
+ * @param[in] texture Valid texture
+ */
+ void SetTexture(const Dali::Texture& texture);
+
+ /**
+ * @brief Downcasts a handle to ParticleRenderer handle.
+ *
+ * If handle points to an ParticleRenderer object, the downcast produces valid handle.
+ * If not, the returned handle is left uninitialized.
+ *
+ * @param[in] handle to An object
+ * @return handle to a ParticleRenderer object or an uninitialized handle
+ */
+ static ParticleRenderer DownCast(BaseHandle handle);
+
+private:
+ /// @cond internal
+ /**
+ * @brief This constructor is used by ParticleRenderer::New() methods.
+ *
+ * @param [in] impl A pointer to a newly allocated implementation
+ */
+ ParticleRenderer(Internal::ParticleRenderer* impl);
+ /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_RENDERER_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <dali-toolkit/internal/particle-system/particle-source-impl.h>
+#include <dali-toolkit/public-api/particle-system/particle-source.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+ParticleSource ParticleSource::New(std::unique_ptr<ParticleSourceInterface>&& sourceUpdater)
+{
+ return {new Internal::ParticleSource(std::move(sourceUpdater))};
+}
+
+ParticleSource::ParticleSource(Internal::ParticleSource* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+ParticleSource ParticleSource::DownCast(BaseHandle handle)
+{
+ return {dynamic_cast<Internal::ParticleSource*>(handle.GetObjectPtr())};
+}
+
+ParticleSourceInterface& ParticleSource::GetSourceCallback() const
+{
+ return GetImplementation(*this).GetUpdater();
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_SOURCE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_SOURCE_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/signals/callback.h>
+#include <memory>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+namespace Dali::Toolkit::ParticleSystem::Internal
+{
+class ParticleSource;
+}
+
+namespace Dali::Toolkit::ParticleSystem
+{
+class ParticleEmitter;
+
+class ParticleList;
+
+class ParticleDomain;
+
+class ParticleModifier;
+
+class ParticleRenderer;
+
+/**
+ * @brief Interface to override as a particle source
+ *
+ * ParticleSourceInterace allows altering behaviour for
+ * generating new particles.
+ */
+class DALI_TOOLKIT_API ParticleSourceInterface
+{
+public:
+ /**
+ * @brief Adds new particles
+ * @param[in] outList List to write back
+ * @param[in] count Requested particles count
+ *
+ * @return Function should return a Number of emitted particles
+ */
+ virtual uint32_t Update(ParticleList& outList, uint32_t count) = 0;
+
+ /**
+ * @brief Called when source is added to the emitter
+ */
+ virtual void Init() = 0;
+};
+
+/**
+ * @class ParticleSource
+ *
+ * ParticleSource defines a logic associated with particles emission.
+ * The emitter will use ParticleSource in order to spawn new particles.
+ *
+ * ParticleSource manages how, where and how many particles to emit.
+ *
+ * ParticleSource uses an implementation of ParticleSourceInterface when
+ * emitting new particles.
+ */
+class DALI_TOOLKIT_API ParticleSource : public Dali::BaseHandle
+{
+public:
+ /**
+ * @brief Constructor
+ */
+ ParticleSource() = default;
+
+ /**
+ * @brief Destructor
+ */
+ ~ParticleSource() = default;
+
+ /**
+ * @brief Creates a new instance of ParticleSource
+ *
+ * New instance is given an ownership over valid implementation
+ * of a ParticleSourceInferface.
+ *
+ * @param[in] particleSourceCallback Valid implementation of ParticleSourceInterface
+ *
+ * @return New ParticleSource object handle
+ */
+ static ParticleSource New(std::unique_ptr<ParticleSourceInterface>&& particleSourceCallback);
+
+ /**
+ * @brief Creates new ParticleSource with given source callback and arguments
+ *
+ * The function creates a ParticleSource of a given class (T) and constructor
+ * arguments.
+ *
+ * @return New ParticleSource object handle
+ */
+ template<class T, class... Args>
+ static ParticleSource New(Args... args)
+ {
+ return New(std::move(std::make_unique<T>(args...)));
+ }
+
+ /**
+ * @brief Downcasts a handle to ParticleSource handle.
+ *
+ * If handle points to an ParticleSource object, the downcast produces valid handle.
+ * If not, the returned handle is left uninitialized.
+ *
+ * @param[in] handle to An object
+ * @return handle to a ParticleSource object or an uninitialized handle
+ */
+ static ParticleSource DownCast(BaseHandle handle);
+
+ /**
+ * @brief Returns associated particle source callback
+ *
+ * @return Valid reference to associated callback
+ */
+ [[nodiscard]] ParticleSourceInterface& GetSourceCallback() const;
+
+private:
+ /// @cond internal
+ /**
+ * @brief This constructor is used by ParticleSource::New() methods.
+ *
+ * @param [in] impl A pointer to a newly allocated implementation
+ */
+ ParticleSource(Internal::ParticleSource* impl);
+ /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_SOURCE_H
\ No newline at end of file
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_TYPES_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_TYPES_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL_INCLUDES
+#include <cinttypes>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/dali-toolkit-common.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+using ParticleStreamTypeFlagBit = uint32_t;
+
+namespace ParticleStream DALI_TOOLKIT_API
+{
+constexpr ParticleStreamTypeFlagBit POSITION_STREAM_BIT = 1 << 0; ///< 3D Position stream
+constexpr ParticleStreamTypeFlagBit ROTATION_STREAM_BIT = 1 << 1; ///< 3D Rotation stream
+constexpr ParticleStreamTypeFlagBit SCALE_STREAM_BIT = 1 << 2; ///< 3D Scale stream
+constexpr ParticleStreamTypeFlagBit SIZE_STREAM_BIT = 1 << 3;
+constexpr ParticleStreamTypeFlagBit COLOR_STREAM_BIT = 1 << 4;
+constexpr ParticleStreamTypeFlagBit OPACITY_STREAM_BIT = 1 << 5;
+constexpr ParticleStreamTypeFlagBit VELOCITY_STREAM_BIT = 1 << 6;
+constexpr ParticleStreamTypeFlagBit LIFETIME_STREAM_BIT = 1 << 7;
+constexpr ParticleStreamTypeFlagBit ALL_STREAMS = 255;
+constexpr ParticleStreamTypeFlagBit DEFAULT_STREAMS = 255; ///< Default streams
+
+/**
+ * Stream data types
+ */
+enum class StreamDataType
+{
+ FLOAT,
+ FLOAT2,
+ FLOAT3,
+ FLOAT4,
+ INT,
+ INT2,
+ INT3,
+ INT4
+};
+
+} // namespace ParticleStream DALI_TOOLKIT_API
+} // namespace Dali::Toolkit::ParticleSystem
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_TYPES_H
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/particle-system/particle-impl.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+
+Particle Particle::DownCast(BaseHandle handle)
+{
+ return {dynamic_cast<Internal::Particle*>(handle.GetObjectPtr())};
+}
+
+void* Particle::Get(ParticleStreamTypeFlagBit streamBit)
+{
+ return GetImplementation(*this).Get(streamBit);
+}
+
+void* Particle::GetByIndex(uint32_t streamIndex)
+{
+ return GetImplementation(*this).GetByIndex(streamIndex);
+}
+
+Particle::Particle(Dali::Toolkit::ParticleSystem::Internal::Particle* impl)
+: Dali::BaseHandle(impl)
+{
+}
+
+uint32_t Particle::GetIndex() const
+{
+ return GetImplementation(*this).GetIndex();
+}
+
+} // namespace Dali::Toolkit::ParticleSystem
--- /dev/null
+#ifndef DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_H
+#define DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_H
+/*
+ * Copyright (c) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/particle-system/particle-types.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-handle.h>
+
+namespace Dali::Toolkit::ParticleSystem
+{
+namespace Internal
+{
+class Particle;
+}
+
+/**
+ * @class Particle
+ *
+ * Particle represents a single instance of a particle in the particle system.
+ *
+ * Particle provides a simple interface that allows R/W access to selected data stream and
+ * allows ignoring a placement of the particle data within the stream (Particle serves as a
+ * view on particular data within a stream).
+ */
+class DALI_TOOLKIT_API Particle : public BaseHandle
+{
+public:
+ /**
+ * @brief Constructor
+ */
+ Particle() = default;
+
+ /**
+ * @brief Downcasts a handle to Particle handle.
+ *
+ * If handle points to an Particle object, the downcast produces valid handle.
+ * If not, the returned handle is left uninitialized.
+ *
+ * @param[in] handle to An object
+ * @return handle to a Particle object or an uninitialized handle
+ */
+ static Particle DownCast(BaseHandle handle);
+
+ /**
+ * @brief Returns writeable reference to the data for specified stream
+ *
+ * The ParticleStreamTypeFlagBit allows accessing only pre-defined streams
+ * defined by the ParticleSystem. For custom streams GetByIndex() should be used.
+ *
+ * @tparam T type of data
+ * @param[in] streamBit Stream to access data from
+ * @return Reference to the data value
+ */
+ template<class T>
+ T& Get(ParticleStreamTypeFlagBit streamBit)
+ {
+ return *reinterpret_cast<T*>(Get(streamBit));
+ }
+
+ /**
+ * @brief Returns writeable reference to the data for a stream specified by stream index.
+ *
+ * This function allows accessing builtin streams as well as custom ones. The index of a custom
+ * stream should be stored upon creation.
+ *
+ * @tparam T type of data
+ * @param[in] streamIndex Index of stream in the emitter
+ * @return Reference to the data value
+ */
+ template<class T>
+ T& GetByIndex(uint32_t streamIndex)
+ {
+ return *reinterpret_cast<T*>(GetByIndex(streamIndex));
+ }
+
+ /**
+ * @brief Returns an index of particle within emitter data streams.
+ *
+ * @return Index of particle
+ */
+ [[nodiscard]] uint32_t GetIndex() const;
+
+public:
+ /// @cond internal
+ /**
+ * @brief This constructor is used by Particle::New() methods.
+ *
+ * @param [in] impl A pointer to a newly allocated implementation
+ */
+ Particle(Dali::Toolkit::ParticleSystem::Internal::Particle* impl);
+ /// @endcond
+
+private:
+ /// @cond internal
+ /**
+ * @brief Returns pointer to the value
+ *
+ *
+ * The ParticleStreamTypeFlagBit allows accessing only pre-defined streams
+ * defined by the ParticleSystem. For custom streams GetByIndex() should be used.
+ *
+ * @param[in] streamBit Stream to access data from
+ *
+ * @return void* to the memory within stream that stores the data
+ */
+ void* Get(ParticleStreamTypeFlagBit streamBit);
+ /// @endcond
+
+ /// @cond internal
+ /**
+ * @brief Returns writeable reference to the data for a stream specified by stream index.
+ *
+ * This function allows accessing builtin streams as well as custom ones. The index of a custom
+ * stream should be stored upon creation.
+ *
+ * @param[in] streamIndex Index of stream in the emitter
+ *
+ * @return void* to the memory within stream that stores the data
+ */
+ void* GetByIndex(uint32_t streamIndex);
+ /// @endcond
+};
+} // namespace Dali::Toolkit::ParticleSystem
+
+#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_PARTICLE_H
\ No newline at end of file