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 <eunkiki.hong@samsung.com>
/*
- * 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.
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()
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<FrameCallback>(this);
{
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();
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::milliseconds>(std::chrono::duration<double>(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<int> toErase;
- int n = 0;
+ auto& particles = mParticleList.GetActiveParticles();
+
+ static std::vector<uint32_t> toEraseIndices;
+
+ uint32_t n = 0;
for(auto& p : particles)
{
auto& lifetime = p.Get<float>(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
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
return;
}
- auto partial = mParticleList.GetActiveParticleCount() / workerThreads;
+ const auto partial = activeCount / workerThreads;
// make tasks
struct UpdateTask
Internal::ParticleModifier& mModifier;
ParticleSystem::ParticleList& mList;
- uint32_t mFirst;
- uint32_t mCount;
+ const uint32_t mFirst;
+ const uint32_t mCount;
void Update()
{
}
};
- std::vector<UpdateTask> updateTasks;
+ static std::vector<UpdateTask> updateTasks;
updateTasks.reserve(workerThreads);
- std::vector<Task> tasks;
+
+ static std::vector<Task> 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()
// NOTE: this function shouldn't be called from multiple thread anyway
if(!gThreadPool)
{
- std::call_once(onceFlag, [&threadPool = gThreadPool] { threadPool = std::make_unique<Dali::ThreadPool>();
+ std::call_once(onceFlag, [&threadPool = gThreadPool]
+ { threadPool = std::make_unique<Dali::ThreadPool>();
threadPool->Initialize(4u); });
}
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);
[[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
uint32_t mEmissionRatePerSecond{1u};
std::atomic<uint32_t> mEmissionCountOnStart{0u};
std::atomic<uint32_t> mActiveParticlesLimit{0u}; ///< 0 - unlimited
- std::atomic<bool> mSystemStarted{false};
+ std::atomic<uint8_t > mSystemStarted{0u}; ///< 0 - not started, 1 - started
std::chrono::milliseconds mCurrentMilliseconds{0};
std::chrono::milliseconds mLastUpdateMs{0};
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<uint8_t*>(mOwnerList.GetDefaultStream(streamBit)) + (mIndex * dataSize);
}
void* Particle::GetByIndex(uint32_t streamIndex)
{
- auto dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex);
- auto* ptr = reinterpret_cast<uint8_t*>(mOwnerList.GetRawStream(streamIndex));
+ const auto dataSize = mOwnerList.GetStreamDataTypeSize(streamIndex);
+ auto* ptr = reinterpret_cast<uint8_t*>(mOwnerList.GetRawStream(streamIndex));
return reinterpret_cast<uint8_t*>(ptr + (mIndex * dataSize));
}
/*
- * 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.
// 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;
}
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;
}
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
}
}
-void ParticleList::ReleaseParticle(uint32_t particleIndex)
+void ParticleList::ReleaseParticles(const std::vector<uint32_t>& 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)
#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.
#include <dali-toolkit/public-api/particle-system/particle.h>
// EXTERNAL INCLUDES
-#include <dali/public-api/object/base-object.h>
-#include <dali/public-api/common/vector-wrapper.h>
-#include <dali/public-api/common/list-wrapper.h>
#include <dali/devel-api/common/map-wrapper.h>
+#include <dali/public-api/common/list-wrapper.h>
+#include <dali/public-api/common/vector-wrapper.h>
+#include <dali/public-api/object/base-object.h>
#include <algorithm>
+#include <cstring>
#include <memory>
-
namespace Dali::Toolkit::ParticleSystem::Internal
{
template<class T>
struct ParticleDataStream
{
~ParticleDataStream() = default;
- template<class T>
- ParticleDataStream(uint32_t capacity, const T& defaultValue, ParticleStream::StreamDataType dataType)
- : ParticleDataStream(capacity, sizeof(T), &defaultValue, dataType)
- {
- }
/**
* Creates new stream of requested capacity and (optionally) fills with default data
*/
- ParticleDataStream(uint32_t capacity, uint32_t dataSize, const void* defaultValue, ParticleStream::StreamDataType dataType)
+ 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<const uint8_t*>(defaultValue);
for(auto i = 0u; i < capacity; ++i)
{
- auto dstPtr = data.data() + (i * dataSize);
- std::copy(reinterpret_cast<const uint8_t*>(defaultValue), reinterpret_cast<const uint8_t*>(defaultValue) + dataSize, dstPtr);
+ 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;
}
/**
ParticleStream::StreamDataType type;
std::vector<uint8_t> data;
std::string streamName;
- uint32_t alive{0u};
- uint32_t capacity;
uint32_t dataSize;
bool localStream{true};
};
class ParticleList : public Dali::BaseObject
{
public:
-
ParticleList(uint32_t capacity, ParticleSystem::ParticleList::ParticleStreamTypeFlags streamFlags);
~ParticleList();
std::list<ParticleSystem::Particle>& GetParticles();
- void ReleaseParticle(uint32_t particleIndex);
+ void ReleaseParticles(const std::vector<uint32_t>& sortedEraseIndices);
uint32_t GetStreamElementSize(bool includeLocalStream);
/*
- * 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.
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);
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";
}
}
*/
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\
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<Quad2D> 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);
mStreamBuffer = vertexBuffer1;
// Set some initial data for streambuffer to force initialization
- std::vector<uint8_t> data;
+ Dali::Vector<uint8_t> data;
+
// Resize using only-non local streams
- auto elementSize = mEmitter->GetParticleList().GetParticleDataSize(false);
- data.resize(elementSize *
- mEmitter->GetParticleList().GetCapacity() * 6u);
- mStreamBuffer.SetData(data.data(), mEmitter->GetParticleList().GetCapacity() * 6u); // needed to initialize
+ 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));
}
}
-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;
auto* dst = reinterpret_cast<uint8_t*>(streamData);
- auto& particles = list.GetParticles();
-
// prepare worker threads
- auto workerCount = GetThreadPool().GetWorkerCount();
+ static const auto workerCount = GetThreadPool().GetWorkerCount();
// divide particles if over the threshold
else
{
// Partial to handle
- auto partialSize = (particleCount / workerCount);
+ const auto partial = particleCount / workerCount;
struct UpdateTask
{
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<UpdateTask> tasks;
+ static std::vector<UpdateTask> tasks;
tasks.reserve(workerCount);
- std::vector<Task> taskQueue;
- auto count = partialSize;
+
+ static std::vector<Task> 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<uint8_t*>(s);
-
- // Size of data
- auto dataSize = list.GetStreamDataTypeSize(s);
-
- memcpy(dst, valuePtr, dataSize);
- dst += dataSize;
- }
- }
- // Replicate data 5 more times for each vertex (GLES2)
- memcpy(dst, particleDst, elementSize);
- dst += elementSize;
- memcpy(dst, particleDst, elementSize);
- dst += elementSize;
- memcpy(dst, particleDst, elementSize);
- dst += elementSize;
- memcpy(dst, particleDst, elementSize);
- dst += elementSize;
- memcpy(dst, particleDst, elementSize);
- dst += elementSize;
- }
+ 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
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);
}
}
// 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;
+ }
}
}
*/
int GetDefaultStreamIndex(ParticleStreamTypeFlagBit defaultStreamBit);
+ /**
+ * @brief Returns raw data container of the particle list
+ * @return list of particles
+ */
std::list<Particle>& GetActiveParticles();
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