From: Eunki Hong Date: Wed, 30 Apr 2025 16:17:46 +0000 (+0900) Subject: (ParticleSystem) Minor optimize and clean up codes (phase 1) X-Git-Tag: accepted/tizen/unified/20250515.075548~3^2~1^2~1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=32eba989d1e469787b54a4560b23dfbd3f8ccbdd;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git (ParticleSystem) Minor optimize and clean up codes (phase 1) Several codes are not consider performances. Let we make codes more clean and faster at UpdateRenderThread. Also, remain some comments what we need to to in future. Change-Id: I221d66fa3967b54d1d11b9c126c03c0c863f1ffd Signed-off-by: Eunki Hong --- diff --git a/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp b/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp index 150d857b90..60a852689a 100644 --- a/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp +++ b/dali-toolkit/internal/particle-system/particle-emitter-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 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. @@ -134,7 +134,10 @@ ParticleSystem::ParticleModifier ParticleEmitter::GetModifierAt(uint32_t index) void ParticleEmitter::RemoveModifierAt(uint32_t index) { - mModifiers.erase(mModifiers.begin() + index); + if(DALI_LIKELY(index < mModifiers.size())) + { + mModifiers.erase(mModifiers.begin() + index); + } } void ParticleEmitter::Start() @@ -148,7 +151,7 @@ void ParticleEmitter::Start() GetImplementation(mParticleRenderer).Initialize(); - mSystemStarted = true; + mSystemStarted.store(1u, std::memory_order_relaxed); mParticleStatusBits &= ~(SIMULATION_STOPPED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT); mParticleStatusBits |= SIMULATION_STARTED_STATUS_BIT; mFrameCallback = std::make_unique(this); @@ -164,7 +167,7 @@ void ParticleEmitter::Stop() { if(mActor && IsComplete() && (mParticleStatusBits & SIMULATION_STARTED_STATUS_BIT)) { - mSystemStarted = false; + mSystemStarted.store(0u, std::memory_order_relaxed); mParticleStatusBits &= ~(SIMULATION_STARTED_STATUS_BIT | SIMULATION_PAUSED_STATUS_BIT); mParticleStatusBits |= SIMULATION_STOPPED_STATUS_BIT; auto renderer = GetImplementation(mParticleRenderer).GetRenderer(); @@ -182,70 +185,67 @@ std::chrono::milliseconds ParticleEmitter::GetCurrentTimeMillis() const void ParticleEmitter::Update() { - // Do not update if emitter setup isn't complete - if(!IsComplete()) - { - return; - } - - auto ms = GetCurrentTimeMillis(); + auto currentUpdateMs = GetCurrentTimeMillis(); if(mCurrentMilliseconds.count() == 0) { - mCurrentMilliseconds = ms; + mCurrentMilliseconds = currentUpdateMs; } if(mLastUpdateMs.count() == 0) { - mLastUpdateMs = ms; + mLastUpdateMs = currentUpdateMs; } + // TODO : Make below codes as thread safe! See mSystemStarted implementation + 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; + auto diffTime = double((currentUpdateMs - mCurrentMilliseconds).count()) / 1000.0; uint32_t emissionCount = 0u; if(diffTime >= emissionDelta) { emissionCount = round(diffTime / emissionDelta); - mCurrentMilliseconds = ms; + mCurrentMilliseconds = mCurrentMilliseconds + std::chrono::duration_cast(std::chrono::duration(emissionCount * emissionDelta)); } // Update lifetimes and discard dead particles - auto& particles = mParticleList.GetActiveParticles(); - auto dt = ms - mLastUpdateMs; - if(dt.count()) + const auto deltaMs = currentUpdateMs - mLastUpdateMs; + + mLastUpdateMs = currentUpdateMs; + + const float deltaSeconds = float(deltaMs.count()) / 1000.0f; + if(deltaMs.count()) { - std::vector toErase; - int n = 0; + auto& particles = mParticleList.GetActiveParticles(); + + static std::vector toEraseIndices; + + uint32_t n = 0; for(auto& p : particles) { auto& lifetime = p.Get(ParticleStream::LIFETIME_STREAM_BIT); - lifetime -= (float(dt.count()) / 1000.0f); + lifetime -= deltaSeconds; if(lifetime <= 0.0f) { - toErase.emplace_back(n); + toEraseIndices.emplace_back(n); } ++n; } - if(!toErase.empty()) + if(!toEraseIndices.empty()) { - int indexShift = 0; - for(auto& v : toErase) - { - GetImplementation(mParticleList).ReleaseParticle(v - indexShift); - ++indexShift; - } + GetImplementation(mParticleList).ReleaseParticles(toEraseIndices); + toEraseIndices.clear(); } } - mLastUpdateMs = ms; // apply initial emission count - if(mSystemStarted) + const bool isFirstFrameAfterStart = mSystemStarted.fetch_and(0u, std::memory_order_relaxed); + if(isFirstFrameAfterStart) { - emissionCount = mEmissionCountOnStart; - mSystemStarted = false; + emissionCount = mEmissionCountOnStart; } // Update source if there are any particles to be emitted @@ -298,9 +298,11 @@ void ParticleEmitter::UpdateSource(uint32_t count) void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleModifier& modifier) { - auto& threadPool = GetThreadPool(); - auto workerThreads = threadPool.GetWorkerCount(); - auto activeCount = mParticleList.GetActiveParticleCount(); + auto& threadPool = GetThreadPool(); + + static const auto workerThreads = threadPool.GetWorkerCount(); + + const auto activeCount = mParticleList.GetActiveParticleCount(); // at least 10 particles per worker thread (should be parametrized) // If less, continue ST @@ -310,7 +312,7 @@ void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleMo return; } - auto partial = mParticleList.GetActiveParticleCount() / workerThreads; + const auto partial = activeCount / workerThreads; // make tasks struct UpdateTask @@ -325,8 +327,8 @@ void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleMo Internal::ParticleModifier& mModifier; ParticleSystem::ParticleList& mList; - uint32_t mFirst; - uint32_t mCount; + const uint32_t mFirst; + const uint32_t mCount; void Update() { @@ -334,25 +336,28 @@ void ParticleEmitter::UpdateModifierMT(Dali::Toolkit::ParticleSystem::ParticleMo } }; - std::vector updateTasks; + static std::vector updateTasks; updateTasks.reserve(workerThreads); - std::vector tasks; + + static std::vector tasks; + tasks.reserve(workerThreads); for(auto i = 0u; i < workerThreads; ++i) { - auto index = i * partial; - auto count = partial; - if(i == workerThreads - 1 && index + count < activeCount) - { - count = activeCount - index; - } + const auto index = i * partial; + const auto count = (i == workerThreads - 1) ? activeCount - index : partial; updateTasks.emplace_back(GetImplementation(modifier), mParticleList, index, count); - tasks.emplace_back([&task = updateTasks.back()](uint32_t n) { task.Update(); }); + tasks.emplace_back([&task = updateTasks.back()](uint32_t n) + { task.Update(); }); } auto future = threadPool.SubmitTasks(tasks, 0); future->Wait(); + + // clear tasks + updateTasks.clear(); + tasks.clear(); } void ParticleEmitter::UpdateDomain() @@ -449,7 +454,8 @@ Dali::ThreadPool& GetThreadPool() // NOTE: this function shouldn't be called from multiple thread anyway if(!gThreadPool) { - std::call_once(onceFlag, [&threadPool = gThreadPool] { threadPool = std::make_unique(); + std::call_once(onceFlag, [&threadPool = gThreadPool] + { threadPool = std::make_unique(); threadPool->Initialize(4u); }); } diff --git a/dali-toolkit/internal/particle-system/particle-emitter-impl.h b/dali-toolkit/internal/particle-system/particle-emitter-impl.h index 8b2459e1b1..7e7bfc7364 100644 --- a/dali-toolkit/internal/particle-system/particle-emitter-impl.h +++ b/dali-toolkit/internal/particle-system/particle-emitter-impl.h @@ -61,6 +61,8 @@ public: return (mParticleStatusBits & STATUS_COMPLETE_BITS) == STATUS_COMPLETE_BITS; } + // TODO : Split below API per thread accessness. + [[nodiscard]] ParticleSystem::ParticleSource GetSource() const; void SetSource(const ParticleSystem::ParticleSource& source); @@ -119,6 +121,7 @@ public: [[nodiscard]] ParticleSystem::ParticleEmitter::Status GetStatus() const; + // DevNote : Need to be define for create fake time getter at UTC [[nodiscard]] std::chrono::milliseconds GetCurrentTimeMillis() const; // All these bits must be set in order to consider emitter COMPLETE @@ -151,7 +154,7 @@ public: uint32_t mEmissionRatePerSecond{1u}; std::atomic mEmissionCountOnStart{0u}; std::atomic mActiveParticlesLimit{0u}; ///< 0 - unlimited - std::atomic mSystemStarted{false}; + std::atomic mSystemStarted{0u}; ///< 0 - not started, 1 - started std::chrono::milliseconds mCurrentMilliseconds{0}; std::chrono::milliseconds mLastUpdateMs{0}; diff --git a/dali-toolkit/internal/particle-system/particle-impl.cpp b/dali-toolkit/internal/particle-system/particle-impl.cpp index 2a5ed3bf14..476233011a 100644 --- a/dali-toolkit/internal/particle-system/particle-impl.cpp +++ b/dali-toolkit/internal/particle-system/particle-impl.cpp @@ -29,15 +29,15 @@ Particle::Particle(Internal::ParticleList& ownerList, uint32_t index) void* Particle::Get(ParticleStreamTypeFlagBit streamBit) { - auto streamIndex = mOwnerList.GetDefaultStreamIndex(streamBit); - auto dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex); + const auto streamIndex = mOwnerList.GetDefaultStreamIndex(streamBit); + const 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)); + const auto dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex); + auto* ptr = reinterpret_cast(mOwnerList.GetRawStream(streamIndex)); return reinterpret_cast(ptr + (mIndex * dataSize)); } diff --git a/dali-toolkit/internal/particle-system/particle-list-impl.cpp b/dali-toolkit/internal/particle-system/particle-list-impl.cpp index 2115a365ea..93bd6e58ff 100644 --- a/dali-toolkit/internal/particle-system/particle-list-impl.cpp +++ b/dali-toolkit/internal/particle-system/particle-list-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 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. @@ -55,45 +55,37 @@ ParticleList::ParticleList(uint32_t capacity, ParticleSystem::ParticleList::Part // 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; + mBuiltInStreamMap[uint32_t(ParticleStream::POSITION_STREAM_BIT)] = AddStream(Vector3::ZERO, "aStreamPosition", false); } if(streamFlags & ParticleStream::ROTATION_STREAM_BIT) { - AddStream(Vector4::ZERO, "aStreamRotation", false); - mBuiltInStreamMap[uint32_t(ParticleStream::ROTATION_STREAM_BIT)] = mDataStreams.size() - 1; + mBuiltInStreamMap[uint32_t(ParticleStream::ROTATION_STREAM_BIT)] = AddStream(Vector4::ZERO, "aStreamRotation", false); } if(streamFlags & ParticleStream::SCALE_STREAM_BIT) { - AddStream(Vector3::ONE, "aStreamScale", false); - mBuiltInStreamMap[uint32_t(ParticleStream::SCALE_STREAM_BIT)] = mDataStreams.size() - 1; + mBuiltInStreamMap[uint32_t(ParticleStream::SCALE_STREAM_BIT)] = AddStream(Vector3::ONE, "aStreamScale", false); } if(streamFlags & ParticleStream::VELOCITY_STREAM_BIT) { - AddStream(Vector3::ZERO, "aStreamVelocity", false); - mBuiltInStreamMap[uint32_t(ParticleStream::VELOCITY_STREAM_BIT)] = mDataStreams.size() - 1; + mBuiltInStreamMap[uint32_t(ParticleStream::VELOCITY_STREAM_BIT)] = AddStream(Vector3::ZERO, "aStreamVelocity", false); } if(streamFlags & ParticleStream::COLOR_STREAM_BIT) { - AddStream(Color::YELLOW, "aStreamColor", false); - mBuiltInStreamMap[uint32_t(ParticleStream::COLOR_STREAM_BIT)] = mDataStreams.size() - 1; + mBuiltInStreamMap[uint32_t(ParticleStream::COLOR_STREAM_BIT)] = AddStream(Color::YELLOW, "aStreamColor", false); } if(streamFlags & ParticleStream::OPACITY_STREAM_BIT) { - AddStream(0.0f, "aStreamOpacity", false); - mBuiltInStreamMap[uint32_t(ParticleStream::OPACITY_STREAM_BIT)] = mDataStreams.size() - 1; + mBuiltInStreamMap[uint32_t(ParticleStream::OPACITY_STREAM_BIT)] = AddStream(0.0f, "aStreamOpacity", false); } if(streamFlags & ParticleStream::LIFETIME_STREAM_BIT) { - AddStream(0.0f, "aStreamLifetime", true); - mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_STREAM_BIT)] = mDataStreams.size() - 1; - AddStream(0.0f, "aStreamLifetimeBase", true); - mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_BASE_STREAM_BIT)] = mDataStreams.size() - 1; + mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_STREAM_BIT)] = AddStream(0.0f, "aStreamLifetime", true); + mBuiltInStreamMap[uint32_t(ParticleStream::LIFETIME_BASE_STREAM_BIT)] = AddStream(0.0f, "aStreamLifetimeBase", true); } // create free chain mFreeChain.resize(capacity); - for(auto i = 0u; i < mFreeChain.size(); ++i) + for(auto i = 0u; i + 1 < mFreeChain.size(); ++i) { mFreeChain[i] = i + 1; } @@ -105,25 +97,14 @@ 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); + mDataStreams.emplace_back(new ParticleDataStream(mMaxParticleCount, sizeOfDataType, defaultValue, dataType, streamName, localStream)); // Update element size - mParticleStreamElementSize = 0; - mParticleStreamElementSizeWithLocal = 0; - for(auto& ds : mDataStreams) + if(!localStream) { - if(!ds->localStream) - { - mParticleStreamElementSize += ds->dataSize; - } - mParticleStreamElementSizeWithLocal += ds->dataSize; + mParticleStreamElementSize += sizeOfDataType; } + mParticleStreamElementSizeWithLocal += sizeOfDataType; return mDataStreams.size() - 1; } @@ -174,13 +155,14 @@ uint32_t ParticleList::GetStreamDataTypeSize(uint32_t streamIndex) const ParticleSystem::Particle ParticleList::NewParticle(float lifetime) { - if(mParticles.size() < mMaxParticleCount) + if(mAliveParticleCount < mMaxParticleCount) { auto newIndex = int32_t(mFreeIndex); mFreeIndex = int32_t(mFreeChain[mFreeIndex]); mAliveParticleCount++; // Add particle + // TODO : Could we use a pool allocator here? mParticles.emplace_back(new Internal::Particle(*this, newIndex)); // Set particle lifetime @@ -208,22 +190,32 @@ uint32_t ParticleList::GetStreamElementSize(bool includeLocalStream) } } -void ParticleList::ReleaseParticle(uint32_t particleIndex) +void ParticleList::ReleaseParticles(const std::vector& sortedEraseIndices) { auto it = mParticles.begin(); - std::advance(it, particleIndex); - // Point at this slot of memory as next free slot - auto& p = *it; - if(mFreeIndex > -1) + mAliveParticleCount -= sortedEraseIndices.size(); + + uint32_t particleIndex = 0; + for(auto& index : sortedEraseIndices) { + // Find particle in the list + // TODO : Let we remove advancement in future. + while(particleIndex < index) + { + ++it; + ++particleIndex; + } + + // Point at this slot of memory as next free slot + auto& p = *it; mFreeChain[p.GetIndex()] = mFreeIndex; - } - mFreeIndex = p.GetIndex(); + mFreeIndex = p.GetIndex(); - // Remove particle from the list - mParticles.erase(it); - mAliveParticleCount--; + // Remove particle from the list + it = mParticles.erase(it); + ++particleIndex; + } } void* ParticleList::GetDefaultStream(ParticleStreamTypeFlagBit streamBit) diff --git a/dali-toolkit/internal/particle-system/particle-list-impl.h b/dali-toolkit/internal/particle-system/particle-list-impl.h index 6f1ddeab99..4d1533fe01 100644 --- a/dali-toolkit/internal/particle-system/particle-list-impl.h +++ b/dali-toolkit/internal/particle-system/particle-list-impl.h @@ -1,7 +1,7 @@ #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. + * Copyright (c) 2025 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. @@ -22,14 +22,14 @@ #include // EXTERNAL INCLUDES -#include -#include -#include #include +#include +#include +#include #include +#include #include - namespace Dali::Toolkit::ParticleSystem::Internal { template @@ -44,40 +44,27 @@ struct StreamDataTypeWrapper 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) + ParticleDataStream(uint32_t capacity, uint32_t dataSize, const void* defaultValue, ParticleStream::StreamDataType dataType, const char* name, bool localStream) + : type(dataType), + data(capacity * dataSize), + streamName(name ? name : ""), + dataSize(dataSize), + localStream(localStream) { - this->capacity = capacity; - data.resize(capacity * dataSize); if(defaultValue) { + auto* dstPtr = data.data(); + const auto* defaultValuePtr = reinterpret_cast(defaultValue); for(auto i = 0u; i < capacity; ++i) { - auto dstPtr = data.data() + (i * dataSize); - std::copy(reinterpret_cast(defaultValue), reinterpret_cast(defaultValue) + dataSize, dstPtr); + memcpy(dstPtr, defaultValuePtr, dataSize); + dstPtr += dataSize; } } - type = dataType; - alive = 0u; - this->dataSize = dataSize; - } - - void SetStreamName(const char* name) - { - streamName = name; - } - - void SetStreamLocal(bool local) - { - localStream = local; } /** @@ -92,8 +79,6 @@ struct ParticleDataStream ParticleStream::StreamDataType type; std::vector data; std::string streamName; - uint32_t alive{0u}; - uint32_t capacity; uint32_t dataSize; bool localStream{true}; }; @@ -109,7 +94,6 @@ struct ParticleDataStream class ParticleList : public Dali::BaseObject { public: - ParticleList(uint32_t capacity, ParticleSystem::ParticleList::ParticleStreamTypeFlags streamFlags); ~ParticleList(); @@ -168,7 +152,7 @@ public: std::list& GetParticles(); - void ReleaseParticle(uint32_t particleIndex); + void ReleaseParticles(const std::vector& sortedEraseIndices); uint32_t GetStreamElementSize(bool includeLocalStream); diff --git a/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp b/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp index 1176190981..aad839f660 100644 --- a/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp +++ b/dali-toolkit/internal/particle-system/particle-renderer-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * Copyright (c) 2025 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. @@ -29,6 +29,14 @@ namespace Dali::Toolkit::ParticleSystem::Internal { +namespace +{ +/** + * @brief The number of vertex elements per each particle is 6. + */ +static constexpr uint32_t NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE = 6u; +} // namespace + ParticleRenderer::ParticleRenderer() { mStreamBufferUpdateCallback = Dali::VertexBufferUpdateCallback::New(this, &ParticleRenderer::OnStreamBufferUpdate); @@ -112,7 +120,7 @@ void ParticleRenderer::CreateShader() streamAtttributes.Add(key, ATTR_TYPES[dataTypeIndex]); // Add shader attribute line - ss << "INPUT mediump " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n"; + ss << "INPUT highp " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n"; } } @@ -128,31 +136,33 @@ void ParticleRenderer::CreateShader() */ std::string vertexShaderCode = streamAttributesStr + std::string( "//@version 100\n\ - INPUT mediump vec2 aPosition;\n\ - INPUT mediump vec2 aTexCoords;\n\ + precision highp float;\n\ + INPUT highp vec2 aPosition;\n\ + INPUT highp vec2 aTexCoords;\n\ \n\ UNIFORM_BLOCK VertBlock \n\ {\n\ - UNIFORM mediump mat4 uMvpMatrix;\n\ - UNIFORM mediump vec3 uSize;\n\ - UNIFORM lowp vec4 uColor;\n\ + UNIFORM highp mat4 uMvpMatrix;\n\ + UNIFORM highp vec3 uSize;\n\ + UNIFORM lowp vec4 uColor;\n\ };\n\ - OUTPUT mediump vec2 vTexCoord;\n\ + OUTPUT highp 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 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\ + vColor = uColor * aStreamColor;\n\ gl_Position = uMvpMatrix * position ;\n\ }\n"); std::string fragmentShaderCode = {"//@version 100\n\ - INPUT mediump vec2 vTexCoord;\n\ - INPUT mediump vec4 vColor;\n\ + precision highp float;\n\ + INPUT highp vec2 vTexCoord;\n\ + INPUT mediump vec4 vColor;\n\ UNIFORM sampler2D sTexture;\n\ \n\ void main()\n\ @@ -186,10 +196,12 @@ void ParticleRenderer::CreateShader() Vertex2D a5{Vector2(0.0f, 1.0f) - C, Vector2(0.0f, 1.0f)}; } QUAD; + static_assert(sizeof(Quad2D) == sizeof(Vertex2D) * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE, "Quad2D must be 6x Vertex2D"); + std::vector quads; quads.resize(mEmitter->GetParticleList().GetCapacity()); std::fill(quads.begin(), quads.end(), QUAD); - vertexBuffer0.SetData(quads.data(), 6u * quads.size()); + vertexBuffer0.SetData(quads.data(), quads.size() * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE); // Second vertex buffer with stream data VertexBuffer vertexBuffer1 = VertexBuffer::New(streamAtttributes); @@ -213,12 +225,12 @@ void ParticleRenderer::CreateShader() mStreamBuffer = vertexBuffer1; // Set some initial data for streambuffer to force initialization - std::vector data; + Dali::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 + const auto elementSize = mEmitter->GetParticleList().GetParticleDataSize(false); + data.ResizeUninitialized(elementSize * mEmitter->GetParticleList().GetCapacity() * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE); + mStreamBuffer.SetData(data.Begin(), mEmitter->GetParticleList().GetCapacity() * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE); // needed to initialize // Sets up callback mStreamBuffer.SetVertexBufferUpdateCallback(std::move(mStreamBufferUpdateCallback)); @@ -258,33 +270,25 @@ void ParticleRenderer::CreateShader() } } -uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size) +uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t maxBytes) { auto& list = GetImplementation(mEmitter->GetParticleList()); - auto particleCount = list.GetActiveParticleCount(); // active particle count - auto particleMaxCount = list.GetParticleCount(); + const auto particleCount = list.GetActiveParticleCount(); // active particle count if(!particleCount) { return 0; } - auto streamCount = list.GetStreamCount(); + const auto particleMaxCount = list.GetParticleCount(); - 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); - } - } + const auto elementByte = list.GetStreamElementSize(false); // Prepare source buffer (MUST BE OPTIMIZED TO AVOID ALLOCATING AND COPYING!!) - auto totalSize = particleMaxCount * elementSize * 6u; + auto totalSize = particleMaxCount * elementByte * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE; // buffer sizes must match - if(totalSize != size) + if(DALI_UNLIKELY(totalSize != maxBytes)) { // ASSERT here ? return 0; @@ -292,10 +296,8 @@ uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size) auto* dst = reinterpret_cast(streamData); - auto& particles = list.GetParticles(); - // prepare worker threads - auto workerCount = GetThreadPool().GetWorkerCount(); + static const auto workerCount = GetThreadPool().GetWorkerCount(); // divide particles if over the threshold @@ -307,7 +309,7 @@ uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size) else { // Partial to handle - auto partialSize = (particleCount / workerCount); + const auto partial = particleCount / workerCount; struct UpdateTask { @@ -328,72 +330,43 @@ uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size) Internal::ParticleRenderer& owner; Internal::ParticleList& particleList; - uint32_t startIndex; - uint32_t count; + const uint32_t startIndex; + const uint32_t count; uint8_t* ptr; }; - std::vector tasks; + static std::vector tasks; tasks.reserve(workerCount); - std::vector taskQueue; - auto count = partialSize; + + static std::vector taskQueue; + taskQueue.reserve(workerCount); for(auto i = 0u; i < workerCount; ++i) { - auto index = i * partialSize; - count = partialSize; + const auto index = i * partial; + const auto count = i == workerCount - 1 ? particleCount - index : partial; - // 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(); }); + tasks.emplace_back(*this, list, index, count, dst + (elementByte * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE) * index); + 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(); + + // clear tasks + tasks.clear(); + taskQueue.clear(); } // 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; - } + UpdateParticlesTask(list, 0, particleCount, dst); } - return particleCount * 6u; // return number of elements to render + return particleCount * elementByte * NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE; // return byte of elements to render } Renderer ParticleRenderer::GetRenderer() const @@ -404,15 +377,14 @@ Renderer ParticleRenderer::GetRenderer() const void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list, uint32_t particleStartIndex, uint32_t particleCount, - uint8_t* basePtr) + uint8_t* dst) { - auto& particles = list.GetParticles(); - auto streamCount = list.GetStreamCount(); - auto elementSize = list.GetStreamElementSize(false); + const auto streamCount = list.GetStreamCount(); + const auto elementByte = list.GetStreamElementSize(false); - // calculate begin of buffer - uint8_t* dst = (basePtr + (elementSize * 6u) * particleStartIndex); + auto& particles = list.GetParticles(); + // TODO : Let we remove advancement in future. auto it = particles.begin(); std::advance(it, particleStartIndex); @@ -436,16 +408,11 @@ void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list, } } // 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; + for(auto vertexCopyCount = 0u; vertexCopyCount < NUMBER_OF_VERTEX_ELEMENTS_PER_PARTICLE - 1; ++vertexCopyCount) + { + memcpy(dst, particleDst, elementByte); + dst += elementByte; + } } } diff --git a/dali-toolkit/public-api/particle-system/particle-list.h b/dali-toolkit/public-api/particle-system/particle-list.h index 7fea6b645d..ff4ad6f552 100644 --- a/dali-toolkit/public-api/particle-system/particle-list.h +++ b/dali-toolkit/public-api/particle-system/particle-list.h @@ -228,6 +228,10 @@ public: */ int GetDefaultStreamIndex(ParticleStreamTypeFlagBit defaultStreamBit); + /** + * @brief Returns raw data container of the particle list + * @return list of particles + */ std::list& GetActiveParticles(); private: @@ -241,8 +245,7 @@ private: * @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); + uint32_t AddStream(void* defaults, size_t dataTypeSize, ParticleStream::StreamDataType dataType, bool localStream); /// @endcond /// @cond internal