2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali-toolkit/internal/particle-system/particle-emitter-impl.h>
19 #include <dali-toolkit/internal/particle-system/particle-list-impl.h>
20 #include <dali-toolkit/internal/particle-system/particle-renderer-impl.h>
21 #include <dali/devel-api/rendering/renderer-devel.h>
23 #include <dali/devel-api/actors/actor-devel.h>
24 #include <dali/devel-api/common/capabilities.h>
25 #include <dali/graphics-api/graphics-buffer.h>
26 #include <dali/graphics-api/graphics-controller.h>
27 #include <dali/graphics-api/graphics-program.h>
28 #include <dali/graphics-api/graphics-shader.h>
30 namespace Dali::Toolkit::ParticleSystem::Internal
32 ParticleRenderer::ParticleRenderer()
34 mStreamBufferUpdateCallback = Dali::VertexBufferUpdateCallback::New(this, &ParticleRenderer::OnStreamBufferUpdate);
37 void ParticleRenderer::SetEmitter(ParticleSystem::Internal::ParticleEmitter* emitter)
42 void ParticleRenderer::SetTexture(const Dali::Texture& texture)
47 void ParticleRenderer::SetBlendingMode(BlendingMode blendingMode)
49 mBlendingMode = blendingMode;
52 BlendingMode ParticleRenderer::GetBlendingMode() const
57 void ParticleRenderer::CreateShader()
59 // Create shader dynamically
60 auto& list = GetImplementation(mEmitter->GetParticleList());
61 auto streamCount = list.GetStreamCount();
63 static const char* ATTR_GLSL_TYPES[] =
65 "float", "vec2", "vec3", "vec4", "int", "ivec2", "ivec3", "ivec4"};
67 static const Property::Type ATTR_TYPES[] =
69 Property::Type::FLOAT,
70 Property::Type::VECTOR2,
71 Property::Type::VECTOR3,
72 Property::Type::VECTOR4,
73 Property::Type::INTEGER,
74 Property::Type::VECTOR2, // This represents floats but by binary write it shouldn't matter (?)
75 Property::Type::VECTOR3,
76 Property::Type::VECTOR4,
81 Vertex2D(const Vector2& _co, const Vector2& _uv)
90 uint32_t streamElementSize = 0u;
91 Property::Map streamAtttributes;
94 for(auto i = 0u; i < streamCount; ++i)
96 // Don't add local streams to the shader
97 if(!list.IsStreamLocal(i))
99 uint32_t dataTypeSize = list.GetStreamDataTypeSize(i);
100 auto dataTypeIndex = uint32_t(list.GetStreamDataType(i));
101 const auto& streamName = list.GetStreamName(i);
102 streamElementSize += dataTypeSize;
104 if(streamName.empty())
106 snprintf(key, sizeof(key), "aStreamAttr_%d", i);
110 snprintf(key, sizeof(key), "%s", streamName.c_str());
112 streamAtttributes.Add(key, ATTR_TYPES[dataTypeIndex]);
114 // Add shader attribute line
115 ss << "INPUT mediump " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n";
119 auto streamAttributesStr = ss.str();
122 * - The MVP comes from the Actor that the particle renderer is attached to
123 * - Attributes are added dynamically based on the particle system properties
124 * - There are two buffers bound
125 * * Geometry buffer (in this instance, a quad)
126 * * ParticleSystem stream buffer with interleaved data
127 * - ParticleSystem buffer is being updated every frame
129 std::string vertexShaderCode = streamAttributesStr + std::string(
131 "INPUT mediump vec2 aPosition;\n\
132 INPUT mediump vec2 aTexCoords;\n\
134 uniform mediump mat4 uMvpMatrix;\n\
135 uniform mediump vec3 uSize;\n\
136 uniform lowp vec4 uColor;\n\
138 OUTPUT mediump vec2 vTexCoord;\n\
139 OUTPUT mediump vec4 vColor;\n\
143 vec4 pos = vec4(aPosition, 0.0, 1.0) * vec4(aStreamScale, 1.0);\n\
144 vec4 position = pos + vec4(aStreamPosition, 0.0);\n\
145 vTexCoord = aTexCoords;\n\
146 vColor = uColor * aStreamColor;\n\
147 gl_Position = uMvpMatrix * position ;\n\
150 std::string fragmentShaderCode =
152 "INPUT mediump vec2 vTexCoord;\n\
153 INPUT mediump vec4 vColor;\n\
154 uniform sampler2D sTexture;\n\
158 lowp vec4 col = TEXTURE(sTexture, vTexCoord) * vColor;\n\
159 if(col.a < 0.1) { discard; }\
163 mShader = Shader::New(Dali::Shader::GetVertexShaderPrefix() + vertexShaderCode, Dali::Shader::GetFragmentShaderPrefix() + fragmentShaderCode);
164 mGeometry = Geometry::New();
166 // Configure geometry attributes
167 Property::Map geometryMap;
168 geometryMap.Add("aPosition", Dali::Property::VECTOR2);
169 geometryMap.Add("aTexCoords", Dali::Property::VECTOR2);
171 // One vertex buffer with geometry
172 VertexBuffer vertexBuffer0 = VertexBuffer::New(geometryMap);
174 // fill the buffer entirely
176 const static Vector2 C(0.5f, 0.5f);
179 Vertex2D a0{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
180 Vertex2D a1{Vector2(1.0f, 0.0f) - C, Vector2(1.0f, 0.0f)};
181 Vertex2D a2{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
182 Vertex2D a3{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
183 Vertex2D a4{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
184 Vertex2D a5{Vector2(0.0f, 1.0f) - C, Vector2(0.0f, 1.0f)};
187 std::vector<Quad2D> quads;
188 quads.resize(mEmitter->GetParticleList().GetCapacity());
189 std::fill(quads.begin(), quads.end(), QUAD);
190 vertexBuffer0.SetData(quads.data(), 6u * quads.size());
192 // Second vertex buffer with stream data
193 VertexBuffer vertexBuffer1 = VertexBuffer::New(streamAtttributes);
196 * For more efficient stream management we need to support glVertexAttribDivisor() function.
197 * This will allow step 1 attribute per 4 vertices (GLES3+). Problem: DALi doesn't support instancing
199 * For older GLES2 we need to duplicate stream data (4x more memory in case of using a quad geometry)
201 * Point-sprites may be of use in the future (problem: point sprites use screen space)
204 // Based on the particle system, populate buffer
205 mGeometry.AddVertexBuffer(vertexBuffer0);
206 mGeometry.AddVertexBuffer(vertexBuffer1);
208 mGeometry.SetType(Geometry::TRIANGLES);
210 mVertexBuffer = vertexBuffer0;
211 mStreamBuffer = vertexBuffer1;
213 // Set some initial data for streambuffer to force initialization
214 std::vector<uint8_t> data;
215 // Resize using only-non local streams
216 auto elementSize = mEmitter->GetParticleList().GetParticleDataSize(false);
217 data.resize(elementSize *
218 mEmitter->GetParticleList().GetCapacity() * 6u);
219 mStreamBuffer.SetData(data.data(), mEmitter->GetParticleList().GetCapacity() * 6u); // needed to initialize
222 mStreamBuffer.SetVertexBufferUpdateCallback(std::move(mStreamBufferUpdateCallback));
224 mRenderer = Renderer::New(mGeometry, mShader);
226 mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::CONTINUOUSLY);
227 // If no texture created, the substitute rect 2x2 texture will be used
230 mTexture = Texture::New(TextureType::TEXTURE_2D, Pixel::RGBA8888, 2u, 2u);
231 auto* pixelArray = new uint32_t[4]{
232 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
234 auto pixelData = PixelData::New(reinterpret_cast<uint8_t*>(pixelArray), 16, 2, 2, Pixel::Format::RGBA8888, PixelData::DELETE_ARRAY);
235 mTexture.Upload(pixelData);
237 mTextureSet = TextureSet::New();
238 mTextureSet.SetTexture(0, mTexture);
239 mRenderer.SetTextures(mTextureSet);
240 mTextureSet.SetSampler(0, Sampler());
242 if(mBlendingMode == BlendingMode::SCREEN)
244 if(Dali::Capabilities::IsBlendEquationSupported(Dali::DevelBlendEquation::SCREEN))
246 mEmitter->GetActor().SetProperty(Dali::DevelActor::Property::BLEND_EQUATION, Dali::DevelBlendEquation::SCREEN);
248 else // Fallback to default
250 mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
255 mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
259 uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
261 auto& list = GetImplementation(mEmitter->GetParticleList());
263 auto particleCount = list.GetActiveParticleCount(); // active particle count
264 auto particleMaxCount = list.GetParticleCount();
270 auto streamCount = list.GetStreamCount();
272 auto elementSize = 0u; // elements size should be cached (it's also stride of buffer) (in bytes)
273 for(auto i = 0u; i < streamCount; ++i)
275 if(!list.IsStreamLocal(i))
277 elementSize += list.GetStreamDataTypeSize(i);
281 // Prepare source buffer (MUST BE OPTIMIZED TO AVOID ALLOCATING AND COPYING!!)
282 auto totalSize = particleMaxCount * elementSize * 6u;
284 // buffer sizes must match
285 if(totalSize != size)
291 auto* dst = reinterpret_cast<uint8_t*>(streamData);
293 auto& particles = list.GetParticles();
295 // prepare worker threads
296 auto workerCount = GetThreadPool().GetWorkerCount();
298 // divide particles if over the threshold
300 [[maybe_unused]] bool runParallel = true;
301 if(!mEmitter->IsParallelProcessingEnabled() || particleCount < workerCount * 10) // don't run parallel if only a few particles to update
308 auto partialSize = (particleCount / workerCount);
312 UpdateTask(Internal::ParticleRenderer& renderer, Internal::ParticleList& list, uint32_t particleStartIndex, uint32_t particleCount, void* basePtr)
315 startIndex(particleStartIndex),
316 count(particleCount),
317 ptr(reinterpret_cast<uint8_t*>(basePtr))
324 owner.UpdateParticlesTask(particleList, startIndex, count, ptr);
327 Internal::ParticleRenderer& owner;
328 Internal::ParticleList& particleList;
334 std::vector<UpdateTask> tasks;
335 tasks.reserve(workerCount);
336 std::vector<Task> taskQueue;
337 auto count = partialSize;
339 for(auto i = 0u; i < workerCount; ++i)
341 auto index = i * partialSize;
344 // make sure there's no leftover particles!
345 if(i == workerCount - 1 && index + count < particleCount)
347 count = particleCount - index;
350 tasks.emplace_back(*this, list, index, count, streamData);
351 taskQueue.emplace_back([&t = tasks.back()](uint32_t threadId) { t.Update(); });
354 // Execute worker tasks
355 auto future = GetThreadPool().SubmitTasks(taskQueue, 0);
360 // less particles so run on a single thread
363 for(auto& p : particles)
365 // without instancing we need to duplicate data 4 times per each quad
366 auto* particleDst = dst;
367 for(auto s = 0u; s < streamCount; ++s)
369 if(!list.IsStreamLocal(s))
371 // Pointer to stream value
372 auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
375 auto dataSize = list.GetStreamDataTypeSize(s);
377 memcpy(dst, valuePtr, dataSize);
381 // Replicate data 5 more times for each vertex (GLES2)
382 memcpy(dst, particleDst, elementSize);
384 memcpy(dst, particleDst, elementSize);
386 memcpy(dst, particleDst, elementSize);
388 memcpy(dst, particleDst, elementSize);
390 memcpy(dst, particleDst, elementSize);
394 return particleCount * 6u; // return number of elements to render
397 Renderer ParticleRenderer::GetRenderer() const
402 void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list,
403 uint32_t particleStartIndex,
404 uint32_t particleCount,
407 auto& particles = list.GetParticles();
408 auto streamCount = list.GetStreamCount();
409 auto elementSize = list.GetStreamElementSize(false);
411 // calculate begin of buffer
412 uint8_t* dst = (basePtr + (elementSize * 6u) * particleStartIndex);
414 auto it = particles.begin();
415 std::advance(it, particleStartIndex);
417 for(; particleCount; particleCount--, it++)
419 ParticleSystem::Particle& p = *it;
420 // without instancing we need to duplicate data 4 times per each quad
421 auto* particleDst = dst;
422 for(auto s = 0u; s < streamCount; ++s)
424 if(!list.IsStreamLocal(s))
426 // Pointer to stream value
427 auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
430 auto dataSize = list.GetStreamDataTypeSize(s);
432 memcpy(dst, valuePtr, dataSize);
436 // Replicate data 5 more times for each vertex (GLES2)
437 memcpy(dst, particleDst, elementSize);
439 memcpy(dst, particleDst, elementSize);
441 memcpy(dst, particleDst, elementSize);
443 memcpy(dst, particleDst, elementSize);
445 memcpy(dst, particleDst, elementSize);
450 bool ParticleRenderer::Initialize()
462 void ParticleRenderer::PrepareToDie()
466 mStreamBuffer.ClearVertexBufferUpdateCallback();
470 } // namespace Dali::Toolkit::ParticleSystem::Internal