Particle System 30/291730/14
authorAdam Bialogonski <adam.b@samsung.com>
Fri, 12 May 2023 11:18:44 +0000 (12:18 +0100)
committerAdam Bialogonski <adam.b@samsung.com>
Fri, 12 May 2023 15:33:52 +0000 (16:33 +0100)
Implementation of DALi Particle System.

Change-Id: I33783236361b31f32460160ea42cb5833b83dc2c

33 files changed:
automated-tests/src/dali-toolkit/CMakeLists.txt
automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp [new file with mode: 0644]
dali-toolkit/internal/file.list
dali-toolkit/internal/particle-system/particle-domain-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-domain-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-emitter-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-emitter-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-list-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-list-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-modifier-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-modifier-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-renderer-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-renderer-impl.h [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-source-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/particle-system/particle-source-impl.h [new file with mode: 0644]
dali-toolkit/public-api/file.list
dali-toolkit/public-api/particle-system/particle-domain.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-domain.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-emitter.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-emitter.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-list.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-list.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-modifier.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-modifier.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-renderer.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-renderer.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-source.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-source.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle-types.h [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle.cpp [new file with mode: 0644]
dali-toolkit/public-api/particle-system/particle.h [new file with mode: 0644]

index d07288b..8ce883d 100755 (executable)
@@ -31,6 +31,7 @@ SET(TC_SOURCES
   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
@@ -183,7 +184,7 @@ ADD_CUSTOM_COMMAND(
 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}
diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp
new file mode 100644 (file)
index 0000000..7ef0b0e
--- /dev/null
@@ -0,0 +1,886 @@
+/*
+ * 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
index b859b39..3ac6c81 100644 (file)
@@ -13,6 +13,13 @@ SET( toolkit_src_files
    ${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
diff --git a/dali-toolkit/internal/particle-system/particle-domain-impl.cpp b/dali-toolkit/internal/particle-system/particle-domain-impl.cpp
new file mode 100644 (file)
index 0000000..cbdee9b
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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
diff --git a/dali-toolkit/internal/particle-system/particle-domain-impl.h b/dali-toolkit/internal/particle-system/particle-domain-impl.h
new file mode 100644 (file)
index 0000000..5e3e1ea
--- /dev/null
@@ -0,0 +1,57 @@
+#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
diff --git a/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp b/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp
new file mode 100644 (file)
index 0000000..e55a0e2
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+ * 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
diff --git a/dali-toolkit/internal/particle-system/particle-emitter-impl.h b/dali-toolkit/internal/particle-system/particle-emitter-impl.h
new file mode 100644 (file)
index 0000000..2f1dcc7
--- /dev/null
@@ -0,0 +1,186 @@
+#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
diff --git a/dali-toolkit/internal/particle-system/particle-impl.cpp b/dali-toolkit/internal/particle-system/particle-impl.cpp
new file mode 100644 (file)
index 0000000..2a5ed3b
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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
diff --git a/dali-toolkit/internal/particle-system/particle-impl.h b/dali-toolkit/internal/particle-system/particle-impl.h
new file mode 100644 (file)
index 0000000..08aea33
--- /dev/null
@@ -0,0 +1,74 @@
+#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
diff --git a/dali-toolkit/internal/particle-system/particle-list-impl.cpp b/dali-toolkit/internal/particle-system/particle-list-impl.cpp
new file mode 100644 (file)
index 0000000..ad4960f
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * 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
diff --git a/dali-toolkit/internal/particle-system/particle-list-impl.h b/dali-toolkit/internal/particle-system/particle-list-impl.h
new file mode 100644 (file)
index 0000000..dbb6615
--- /dev/null
@@ -0,0 +1,230 @@
+#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
diff --git a/dali-toolkit/internal/particle-system/particle-modifier-impl.cpp b/dali-toolkit/internal/particle-system/particle-modifier-impl.cpp
new file mode 100644 (file)
index 0000000..c00d71c
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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
diff --git a/dali-toolkit/internal/particle-system/particle-modifier-impl.h b/dali-toolkit/internal/particle-system/particle-modifier-impl.h
new file mode 100644 (file)
index 0000000..75cbf5f
--- /dev/null
@@ -0,0 +1,64 @@
+#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
diff --git a/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp b/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp
new file mode 100644 (file)
index 0000000..b86c434
--- /dev/null
@@ -0,0 +1,463 @@
+/*
+ * 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
diff --git a/dali-toolkit/internal/particle-system/particle-renderer-impl.h b/dali-toolkit/internal/particle-system/particle-renderer-impl.h
new file mode 100644 (file)
index 0000000..2c41eb9
--- /dev/null
@@ -0,0 +1,117 @@
+#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
diff --git a/dali-toolkit/internal/particle-system/particle-source-impl.cpp b/dali-toolkit/internal/particle-system/particle-source-impl.cpp
new file mode 100644 (file)
index 0000000..3d5c0a3
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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
diff --git a/dali-toolkit/internal/particle-system/particle-source-impl.h b/dali-toolkit/internal/particle-system/particle-source-impl.h
new file mode 100644 (file)
index 0000000..21692ac
--- /dev/null
@@ -0,0 +1,67 @@
+#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
index ffd306b..833bfbf 100644 (file)
@@ -32,6 +32,13 @@ SET( public_api_src_files
   ${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
@@ -157,6 +164,17 @@ SET( public_api_visuals_header_files
   ${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
@@ -192,4 +210,5 @@ SET( PUBLIC_API_HEADERS ${PUBLIC_API_HEADERS}
   ${public_api_visuals_header_files}
   ${public_api_camera_view_header_files}
   ${public_api_gl_view_header_files}
+  ${public_api_particle_system_header_files}
 )
diff --git a/dali-toolkit/public-api/particle-system/particle-domain.cpp b/dali-toolkit/public-api/particle-system/particle-domain.cpp
new file mode 100644 (file)
index 0000000..135926a
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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
diff --git a/dali-toolkit/public-api/particle-system/particle-domain.h b/dali-toolkit/public-api/particle-system/particle-domain.h
new file mode 100644 (file)
index 0000000..b7b103d
--- /dev/null
@@ -0,0 +1,79 @@
+#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
diff --git a/dali-toolkit/public-api/particle-system/particle-emitter.cpp b/dali-toolkit/public-api/particle-system/particle-emitter.cpp
new file mode 100644 (file)
index 0000000..6a3e746
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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
diff --git a/dali-toolkit/public-api/particle-system/particle-emitter.h b/dali-toolkit/public-api/particle-system/particle-emitter.h
new file mode 100644 (file)
index 0000000..15e3aa7
--- /dev/null
@@ -0,0 +1,337 @@
+#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
diff --git a/dali-toolkit/public-api/particle-system/particle-list.cpp b/dali-toolkit/public-api/particle-system/particle-list.cpp
new file mode 100644 (file)
index 0000000..6b1e18a
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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
diff --git a/dali-toolkit/public-api/particle-system/particle-list.h b/dali-toolkit/public-api/particle-system/particle-list.h
new file mode 100644 (file)
index 0000000..2c20159
--- /dev/null
@@ -0,0 +1,273 @@
+#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
diff --git a/dali-toolkit/public-api/particle-system/particle-modifier.cpp b/dali-toolkit/public-api/particle-system/particle-modifier.cpp
new file mode 100644 (file)
index 0000000..472499e
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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
diff --git a/dali-toolkit/public-api/particle-system/particle-modifier.h b/dali-toolkit/public-api/particle-system/particle-modifier.h
new file mode 100644 (file)
index 0000000..de8d4e6
--- /dev/null
@@ -0,0 +1,140 @@
+#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
diff --git a/dali-toolkit/public-api/particle-system/particle-renderer.cpp b/dali-toolkit/public-api/particle-system/particle-renderer.cpp
new file mode 100644 (file)
index 0000000..05e9b40
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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
diff --git a/dali-toolkit/public-api/particle-system/particle-renderer.h b/dali-toolkit/public-api/particle-system/particle-renderer.h
new file mode 100644 (file)
index 0000000..0e0a35b
--- /dev/null
@@ -0,0 +1,98 @@
+#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
diff --git a/dali-toolkit/public-api/particle-system/particle-source.cpp b/dali-toolkit/public-api/particle-system/particle-source.cpp
new file mode 100644 (file)
index 0000000..3051b1d
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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
diff --git a/dali-toolkit/public-api/particle-system/particle-source.h b/dali-toolkit/public-api/particle-system/particle-source.h
new file mode 100644 (file)
index 0000000..d2578a0
--- /dev/null
@@ -0,0 +1,149 @@
+#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
diff --git a/dali-toolkit/public-api/particle-system/particle-types.h b/dali-toolkit/public-api/particle-system/particle-types.h
new file mode 100644 (file)
index 0000000..383865d
--- /dev/null
@@ -0,0 +1,60 @@
+#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
diff --git a/dali-toolkit/public-api/particle-system/particle.cpp b/dali-toolkit/public-api/particle-system/particle.cpp
new file mode 100644 (file)
index 0000000..d94cbae
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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
diff --git a/dali-toolkit/public-api/particle-system/particle.h b/dali-toolkit/public-api/particle-system/particle.h
new file mode 100644 (file)
index 0000000..641ef71
--- /dev/null
@@ -0,0 +1,142 @@
+#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