From 148f8624f56b20a6ea95e21afc124cbeffbedb91 Mon Sep 17 00:00:00 2001 From: Adam Bialogonski Date: Fri, 12 May 2023 12:18:44 +0100 Subject: [PATCH] Particle System Implementation of DALi Particle System. Change-Id: I33783236361b31f32460160ea42cb5833b83dc2c --- automated-tests/src/dali-toolkit/CMakeLists.txt | 3 +- .../src/dali-toolkit/utc-Dali-ParticleSystem.cpp | 886 +++++++++++++++++++++ dali-toolkit/internal/file.list | 7 + .../particle-system/particle-domain-impl.cpp | 27 + .../particle-system/particle-domain-impl.h | 57 ++ .../particle-system/particle-emitter-impl.cpp | 446 +++++++++++ .../particle-system/particle-emitter-impl.h | 186 +++++ .../internal/particle-system/particle-impl.cpp | 49 ++ .../internal/particle-system/particle-impl.h | 74 ++ .../particle-system/particle-list-impl.cpp | 244 ++++++ .../internal/particle-system/particle-list-impl.h | 230 ++++++ .../particle-system/particle-modifier-impl.cpp | 37 + .../particle-system/particle-modifier-impl.h | 64 ++ .../particle-system/particle-renderer-impl.cpp | 463 +++++++++++ .../particle-system/particle-renderer-impl.h | 117 +++ .../particle-system/particle-source-impl.cpp | 41 + .../particle-system/particle-source-impl.h | 67 ++ dali-toolkit/public-api/file.list | 19 + .../public-api/particle-system/particle-domain.cpp | 41 + .../public-api/particle-system/particle-domain.h | 79 ++ .../particle-system/particle-emitter.cpp | 161 ++++ .../public-api/particle-system/particle-emitter.h | 337 ++++++++ .../public-api/particle-system/particle-list.cpp | 105 +++ .../public-api/particle-system/particle-list.h | 273 +++++++ .../particle-system/particle-modifier.cpp | 40 + .../public-api/particle-system/particle-modifier.h | 140 ++++ .../particle-system/particle-renderer.cpp | 48 ++ .../public-api/particle-system/particle-renderer.h | 98 +++ .../public-api/particle-system/particle-source.cpp | 44 + .../public-api/particle-system/particle-source.h | 149 ++++ .../public-api/particle-system/particle-types.h | 60 ++ .../public-api/particle-system/particle.cpp | 49 ++ dali-toolkit/public-api/particle-system/particle.h | 142 ++++ 33 files changed, 4782 insertions(+), 1 deletion(-) create mode 100644 automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-domain-impl.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-domain-impl.h create mode 100644 dali-toolkit/internal/particle-system/particle-emitter-impl.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-emitter-impl.h create mode 100644 dali-toolkit/internal/particle-system/particle-impl.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-impl.h create mode 100644 dali-toolkit/internal/particle-system/particle-list-impl.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-list-impl.h create mode 100644 dali-toolkit/internal/particle-system/particle-modifier-impl.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-modifier-impl.h create mode 100644 dali-toolkit/internal/particle-system/particle-renderer-impl.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-renderer-impl.h create mode 100644 dali-toolkit/internal/particle-system/particle-source-impl.cpp create mode 100644 dali-toolkit/internal/particle-system/particle-source-impl.h create mode 100644 dali-toolkit/public-api/particle-system/particle-domain.cpp create mode 100644 dali-toolkit/public-api/particle-system/particle-domain.h create mode 100644 dali-toolkit/public-api/particle-system/particle-emitter.cpp create mode 100644 dali-toolkit/public-api/particle-system/particle-emitter.h create mode 100644 dali-toolkit/public-api/particle-system/particle-list.cpp create mode 100644 dali-toolkit/public-api/particle-system/particle-list.h create mode 100644 dali-toolkit/public-api/particle-system/particle-modifier.cpp create mode 100644 dali-toolkit/public-api/particle-system/particle-modifier.h create mode 100644 dali-toolkit/public-api/particle-system/particle-renderer.cpp create mode 100644 dali-toolkit/public-api/particle-system/particle-renderer.h create mode 100644 dali-toolkit/public-api/particle-system/particle-source.cpp create mode 100644 dali-toolkit/public-api/particle-system/particle-source.h create mode 100644 dali-toolkit/public-api/particle-system/particle-types.h create mode 100644 dali-toolkit/public-api/particle-system/particle.cpp create mode 100644 dali-toolkit/public-api/particle-system/particle.h diff --git a/automated-tests/src/dali-toolkit/CMakeLists.txt b/automated-tests/src/dali-toolkit/CMakeLists.txt index d07288b..8ce883d 100755 --- a/automated-tests/src/dali-toolkit/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit/CMakeLists.txt @@ -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 index 0000000..7ef0b0e --- /dev/null +++ b/automated-tests/src/dali-toolkit/utc-Dali-ParticleSystem.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace Dali; +using namespace Dali::Toolkit::ParticleSystem; + +/** + * Helper function to invoke next function in the call chain + */ +template +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(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(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(); + 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 mFuture; + std::promise mPromise; + ParticleEmitter mEmitter; +}; + +class TestSource2 : public ParticleSourceInterface +{ +public: + + TestSource2(ParticleEmitter* emitter) + { + mEmitter = *emitter; + } + + void NewFrame() + { + mPromise = std::promise(); + 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(mStreamBasePos); + + [[maybe_unused]] auto& gpos = particle.Get(ParticleStream::POSITION_STREAM_BIT); + [[maybe_unused]] auto& col = particle.Get(ParticleStream::COLOR_STREAM_BIT); + [[maybe_unused]] auto& vel = particle.Get(ParticleStream::VELOCITY_STREAM_BIT); + [[maybe_unused]] auto& sca = particle.Get(ParticleStream::SCALE_STREAM_BIT); + //auto& basePos = particle.Get(ParticleStream::SCALE_STREAM_BIT); + } + + return count; + } + + void Init() override + { + // calls initialized + mStreamBasePos = mEmitter.GetParticleList().AddLocalStream(Vector3::ZERO); + mInitialized = true; + } + + bool mInitialized{false}; + std::future mFuture; + std::promise 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 +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(&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(); + + { + 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(&emitter); + + // Create test renderer + auto renderer = ParticleRenderer::New(); + + // Create modifier + auto modifier = ParticleModifier::New(); + + // 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(&emitter); + + // Create test renderer + auto renderer = ParticleRenderer::New(); + + // Create modifier + auto modifier0 = ParticleModifier::New(); + auto modifier1 = ParticleModifier::New(); + auto modifier2 = ParticleModifier::New(); + + // 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(); + + // 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(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(&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(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(&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(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(&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(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(&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(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(&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(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 diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index b859b39..3ac6c81 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -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 index 0000000..cbdee9b --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-domain-impl.cpp @@ -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 + +// INTERNAL INCLUDES +#include + +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 index 0000000..5e3e1ea --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-domain-impl.h @@ -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 + +// EXTERNAL INCLUDES +#include +#include + +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(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(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 index 0000000..e55a0e2 --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp @@ -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 + +// INTERNAL INCLUDES +#include +#include +#include +#include + +// EXTERNAL INCLUDES +#include +#include +#include +#include + +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(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::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 toErase; + int n = 0; + for(auto& p : particles) + { + auto& lifetime = p.Get(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 updateTasks; + updateTasks.reserve(workerThreads); + std::vector 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 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(); + 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 index 0000000..2f1dcc7 --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-emitter-impl.h @@ -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 +#include +#include +#include +#include + +// For multithreading update +#include + +// INTERNAL INCLUDES +#include +#include +#include +#include +#include +#include + +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 mModifiers; + + ParticleSystem::ParticleRenderer mParticleRenderer; + + Actor mActor; + + uint32_t mEmissionRatePerSecond{1u}; + std::atomic mEmissionCountOnStart{0u}; + std::atomic mActiveParticlesLimit{0u}; ///< 0 - unlimited + std::atomic mSystemStarted{false}; + std::chrono::milliseconds mCurrentMilliseconds{0}; + std::chrono::milliseconds mLastUpdateMs{0}; + + bool mParallelProcessing{false}; + std::unique_ptr 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(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(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 index 0000000..2a5ed3b --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-impl.cpp @@ -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 + +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(mOwnerList.GetDefaultStream(streamBit)) + (mIndex * dataSize); +} + +void* Particle::GetByIndex(uint32_t streamIndex) +{ + auto dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex); + auto* ptr = reinterpret_cast(mOwnerList.GetRawStream(streamIndex)); + return reinterpret_cast(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 index 0000000..08aea33 --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-impl.h @@ -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 + +// EXTERNAL INCLUDES +#include +#include +#include + +// INTERNAL INCLUDES +#include + +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(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(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 index 0000000..ad4960f --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-list-impl.cpp @@ -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 + +// INTERNAL INCLUDES +#include + +// EXTERNAL INCLUDES +#include +#include + +namespace Dali::Toolkit::ParticleSystem::Internal +{ + +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::GetType() +{ + return ParticleStream::StreamDataType::FLOAT3; +} + +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::GetType() +{ + return ParticleStream::StreamDataType::FLOAT4; +} + +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::GetType() +{ + return ParticleStream::StreamDataType::FLOAT2; +} + +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::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(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& 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 index 0000000..dbb6615 --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-list-impl.h @@ -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 +#include + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include +#include + +namespace Dali::Toolkit::ParticleSystem::Internal +{ +template +struct StreamDataTypeWrapper +{ + static ParticleStream::StreamDataType GetType() + { + return {}; + } +}; + +struct ParticleDataStream +{ + ~ParticleDataStream() = default; + template + 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(defaultValue), reinterpret_cast(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 + T* GetAs() + { + return reinterpret_cast(data.data()); + } + + ParticleStream::StreamDataType type; + std::vector 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& GetParticles(); + + void ReleaseParticle(uint32_t particleIndex); + + uint32_t GetStreamElementSize(bool includeLocalStream); + +private: + template + uint32_t AddStream(const T& defaultValue, const char* streamName, bool localStream) + { + return AddStream(sizeof(T), &defaultValue, StreamDataTypeWrapper::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 mBuffer[2]; + + uint32_t mAliveParticleCount{0u}; + uint32_t mMaxParticleCount; + + // Data storage + std::vector> mDataStreams; + + std::vector mFreeChain; + int32_t mFreeIndex{0u}; + + std::map mBuiltInStreamMap; + + std::list 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(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(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 index 0000000..c00d71c --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-modifier-impl.cpp @@ -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 + +namespace Dali::Toolkit::ParticleSystem::Internal +{ +ParticleModifier::ParticleModifier(std::unique_ptr&& 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 index 0000000..75cbf5f --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-modifier-impl.h @@ -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 +#include + +namespace Dali::Toolkit::ParticleSystem::Internal +{ +class ParticleList; +class ParticleEmitter; +class ParticleModifier : public Dali::BaseObject +{ +public: + ParticleModifier(std::unique_ptr&& updater); + + void Update(ParticleSystem::ParticleList& list, uint32_t first, uint32_t count); + + ParticleModifierInterface& GetUpdater(); + +private: + std::unique_ptr 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(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(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 index 0000000..b86c434 --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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 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 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(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(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(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 tasks; + tasks.reserve(workerCount); + std::vector 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(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(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 index 0000000..2c41eb9 --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-renderer-impl.h @@ -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 + +#include +#include +#include + +#include +#include +#include +#include +#include + +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 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(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(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 index 0000000..3d5c0a3 --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-source-impl.cpp @@ -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 +#include +#include +#include + +namespace Dali::Toolkit::ParticleSystem::Internal +{ +ParticleSource::ParticleSource(std::unique_ptr&& 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 index 0000000..21692ac --- /dev/null +++ b/dali-toolkit/internal/particle-system/particle-source-impl.h @@ -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 +#include +#include + +namespace Dali::Toolkit::ParticleSystem::Internal +{ +class ParticleList; +class ParticleEmitter; +class ParticleSource : public Dali::BaseObject +{ +public: + ~ParticleSource() override = default; + + explicit ParticleSource(std::unique_ptr&& sourceUpdater); + + void Update(ParticleSystem::ParticleList& list, uint32_t count); + + [[nodiscard]] ParticleSourceInterface& GetUpdater() const; + +private: + std::unique_ptr 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(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(handle); +} + +} // namespace Dali::Toolkit::ParticleSystem + +#endif // DALI_TOOLKIT_PARTICLE_SYSTEM_INTERNAL_PARTICLE_SOURCE_H \ No newline at end of file diff --git a/dali-toolkit/public-api/file.list b/dali-toolkit/public-api/file.list index ffd306b..833bfbf 100644 --- a/dali-toolkit/public-api/file.list +++ b/dali-toolkit/public-api/file.list @@ -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 index 0000000..135926a --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-domain.cpp @@ -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 + +// INTERNAL INCLUDES +#include + +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(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 index 0000000..b7b103d --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-domain.h @@ -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 + +// EXTERNAL INCLUDES +#include +#include + +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 index 0000000..6a3e746 --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-emitter.cpp @@ -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 + +// INTERNAL INCLUDES +#include +#include + +// EXTERNAL INCLUDES +#include + +namespace Dali::Toolkit::ParticleSystem +{ + +ParticleEmitter ParticleEmitter::New() +{ + return {new Dali::Toolkit::ParticleSystem::Internal::ParticleEmitter()}; +} + +ParticleEmitter ParticleEmitter::DownCast(BaseHandle handle) +{ + return {dynamic_cast(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 index 0000000..15e3aa7 --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-emitter.h @@ -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 + +// EXTERNAL INCLUDES +#include +#include +#include + +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 index 0000000..6b1e18a --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-list.cpp @@ -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 +#include + +namespace Dali::Toolkit::ParticleSystem +{ +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::GetType() +{ + return ParticleStream::StreamDataType::FLOAT3; +} + +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::GetType() +{ + return ParticleStream::StreamDataType::FLOAT4; +} + +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::GetType() +{ + return ParticleStream::StreamDataType::FLOAT2; +} + +template<> +ParticleStream::StreamDataType StreamDataTypeWrapper::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(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& 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 index 0000000..2c20159 --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-list.h @@ -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 + +// EXTERNAL INCLUDES +#include +#include +#include + +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 +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 + inline uint32_t AddStream(T defaults) + { + return AddStream(&defaults, StreamDataTypeWrapper::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 + inline uint32_t AddLocalStream(T defaults) + { + return AddStream(&defaults, sizeof(T), StreamDataTypeWrapper::GetType(), true); + } + + template + T* GetStream(uint32_t streamIndex) + { + return reinterpret_cast(GetRawStream(streamIndex)); + } + + template + T* GetDefaultStream(ParticleStreamTypeFlagBit streamFlagBit) + { + return reinterpret_cast(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& 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 index 0000000..472499e --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-modifier.cpp @@ -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 + +#include +#include + +namespace Dali::Toolkit::ParticleSystem +{ +ParticleModifier ParticleModifier::New(std::unique_ptr&& modifierUpdater) +{ + return {new Internal::ParticleModifier(std::move(modifierUpdater))}; +} + +ParticleModifier::ParticleModifier(Internal::ParticleModifier* impl) +: Dali::BaseHandle(impl) +{ +} + +ParticleModifier ParticleModifier::DownCast(BaseHandle handle) +{ + return {dynamic_cast(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 index 0000000..de8d4e6 --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-modifier.h @@ -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 + +// EXTERNAL INCLUDES +#include +#include +#include + +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&& modifierUpdater); + + /** + * @brief Creates new modifier with given class and construction arguments + * + * @return New ParticleModifier object + */ + template + static ParticleModifier New(Args... args) + { + return New(std::move(std::make_unique(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 index 0000000..05e9b40 --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-renderer.cpp @@ -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 +#include + +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(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 index 0000000..0e0a35b --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-renderer.h @@ -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 + +// EXTERNAL INCLUDES +#include +#include +#include + +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 index 0000000..3051b1d --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-source.cpp @@ -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 +#include + +namespace Dali::Toolkit::ParticleSystem +{ + +ParticleSource ParticleSource::New(std::unique_ptr&& sourceUpdater) +{ + return {new Internal::ParticleSource(std::move(sourceUpdater))}; +} + +ParticleSource::ParticleSource(Internal::ParticleSource* impl) +: Dali::BaseHandle(impl) +{ +} + +ParticleSource ParticleSource::DownCast(BaseHandle handle) +{ + return {dynamic_cast(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 index 0000000..d2578a0 --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-source.h @@ -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 +#include +#include + +// INTERNAL INCLUDES +#include + +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&& 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 + static ParticleSource New(Args... args) + { + return New(std::move(std::make_unique(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 index 0000000..383865d --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle-types.h @@ -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 + +// INTERNAL INCLUDES +#include + +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 index 0000000..d94cbae --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle.cpp @@ -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 + +namespace Dali::Toolkit::ParticleSystem +{ + +Particle Particle::DownCast(BaseHandle handle) +{ + return {dynamic_cast(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 index 0000000..641ef71 --- /dev/null +++ b/dali-toolkit/public-api/particle-system/particle.h @@ -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 + +// EXTERNAL INCLUDES +#include + +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 + T& Get(ParticleStreamTypeFlagBit streamBit) + { + return *reinterpret_cast(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 + T& GetByIndex(uint32_t streamIndex) + { + return *reinterpret_cast(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 -- 2.7.4