Particle System
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / particle-system / particle-renderer-impl.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
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>
22
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>
29
30 namespace Dali::Toolkit::ParticleSystem::Internal
31 {
32 ParticleRenderer::ParticleRenderer()
33 {
34   mStreamBufferUpdateCallback = Dali::VertexBufferUpdateCallback::New(this, &ParticleRenderer::OnStreamBufferUpdate);
35 }
36
37 void ParticleRenderer::SetEmitter(ParticleSystem::Internal::ParticleEmitter* emitter)
38 {
39   mEmitter = emitter;
40 }
41
42 void ParticleRenderer::SetTexture(const Dali::Texture& texture)
43 {
44   mTexture = texture;
45 }
46
47 void ParticleRenderer::SetBlendingMode(BlendingMode blendingMode)
48 {
49   mBlendingMode = blendingMode;
50 }
51
52 void ParticleRenderer::CreateShader()
53 {
54   // Create shader dynamically
55   auto& list        = GetImplementation(mEmitter->GetParticleList());
56   auto  streamCount = list.GetStreamCount();
57
58   static const char* ATTR_GLSL_TYPES[] =
59     {
60       "float", "vec2", "vec3", "vec4", "int", "ivec2", "ivec3", "ivec4"};
61
62   static const Property::Type ATTR_TYPES[] =
63     {
64       Property::Type::FLOAT,
65       Property::Type::VECTOR2,
66       Property::Type::VECTOR3,
67       Property::Type::VECTOR4,
68       Property::Type::INTEGER,
69       Property::Type::VECTOR2, // This represents floats but by binary write it shouldn't matter (?)
70       Property::Type::VECTOR3,
71       Property::Type::VECTOR4,
72     };
73
74   struct Vertex2D
75   {
76     Vertex2D(const Vector2& _co, const Vector2& _uv)
77     : co(_co),
78       uv(_uv)
79     {
80     }
81     Dali::Vector2 co{};
82     Dali::Vector2 uv{};
83   };
84
85   uint32_t      streamElementSize = 0u;
86   Property::Map streamAtttributes;
87
88   std::stringstream ss;
89   for(auto i = 0u; i < streamCount; ++i)
90   {
91     // Don't add local streams to the shader
92     if(!list.IsStreamLocal(i))
93     {
94       uint32_t    dataTypeSize  = list.GetStreamDataTypeSize(i);
95       auto        dataTypeIndex = uint32_t(list.GetStreamDataType(i));
96       const auto& streamName    = list.GetStreamName(i);
97       streamElementSize += dataTypeSize;
98       char key[256];
99       if(streamName.empty())
100       {
101         sprintf(key, "aStreamAttr_%d", i);
102       }
103       else
104       {
105         sprintf(key, "%s", streamName.c_str());
106       }
107       streamAtttributes.Add(key, ATTR_TYPES[dataTypeIndex]);
108
109       // Add shader attribute line
110       ss << "INPUT mediump " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n";
111     }
112   }
113
114   auto streamAttributesStr = ss.str();
115
116   /**
117    * - The MVP comes from the Actor that the particle renderer is attached to
118    * - Attributes are added dynamically based on the particle system properties
119    * - There are two buffers bound
120    *   * Geometry buffer (in this instance, a quad)
121    *   * ParticleSystem stream buffer with interleaved data
122    * - ParticleSystem buffer is being updated every frame
123    */
124   std::string vertexShaderCode = streamAttributesStr + std::string(
125
126                                                          "INPUT mediump vec2 aPosition;\n\
127       INPUT mediump vec2 aTexCoords;\n\
128       \n\
129       uniform mediump mat4   uMvpMatrix;\n\
130       uniform mediump vec3   uSize;\n\
131       uniform lowp vec4      uColor;\n\
132       \
133       OUTPUT mediump vec2   vTexCoord;\n\
134       OUTPUT mediump vec4 vColor;\n\
135       \n\
136       void main()\n\
137       {\n\
138         vec4 pos = vec4(aPosition, 0.0, 1.0) * vec4(aStreamScale, 1.0);\n\
139         vec4 position =  pos + vec4(aStreamPosition, 0.0);\n\
140         vTexCoord     = aTexCoords;\n\
141         vColor = uColor * aStreamColor;\n\
142         gl_Position   = uMvpMatrix * position ;\n\
143       }\n");
144
145   std::string fragmentShaderCode =
146     {
147       "INPUT mediump vec2       vTexCoord;\n\
148     INPUT mediump vec4       vColor;\n\
149     uniform sampler2D sTexture;\n\
150     \n\
151     void main()\n\
152     {\n\
153       lowp vec4 col = TEXTURE(sTexture, vTexCoord) * vColor;\n\
154       if(col.a < 0.1) { discard; }\
155       fragColor = col;\n\
156     }\n"};
157
158   mShader   = Shader::New(Dali::Shader::GetVertexShaderPrefix() + vertexShaderCode, Dali::Shader::GetFragmentShaderPrefix() + fragmentShaderCode);
159   mGeometry = Geometry::New();
160
161   // Configure geometry attributes
162   Property::Map geometryMap;
163   geometryMap.Add("aPosition", Dali::Property::VECTOR2);
164   geometryMap.Add("aTexCoords", Dali::Property::VECTOR2);
165
166   // One vertex buffer with geometry
167   VertexBuffer vertexBuffer0 = VertexBuffer::New(geometryMap);
168
169   // fill the buffer entirely
170   // 2D quad
171   const static Vector2 C(0.5f, 0.5f);
172   struct Quad2D
173   {
174     Vertex2D a0{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
175     Vertex2D a1{Vector2(1.0f, 0.0f) - C, Vector2(1.0f, 0.0f)};
176     Vertex2D a2{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
177     Vertex2D a3{Vector2(0.0f, 0.0f) - C, Vector2(0.0f, 0.0f)};
178     Vertex2D a4{Vector2(1.0f, 1.0f) - C, Vector2(1.0f, 1.0f)};
179     Vertex2D a5{Vector2(0.0f, 1.0f) - C, Vector2(0.0f, 1.0f)};
180   } QUAD;
181
182   std::vector<Quad2D> quads;
183   quads.resize(mEmitter->GetParticleList().GetCapacity());
184   std::fill(quads.begin(), quads.end(), QUAD);
185   vertexBuffer0.SetData(quads.data(), 6u * quads.size());
186
187   // Second vertex buffer with stream data
188   VertexBuffer vertexBuffer1 = VertexBuffer::New(streamAtttributes);
189
190   /**
191    * For more efficient stream management we need to support glVertexAttribDivisor() function.
192    * This will allow step 1 attribute per 4 vertices (GLES3+). Problem: DALi doesn't support instancing
193    *
194    * For older GLES2 we need to duplicate stream data (4x more memory in case of using a quad geometry)
195    *
196    * Point-sprites may be of use in the future (problem: point sprites use screen space)
197    */
198
199   // Based on the particle system, populate buffer
200   mGeometry.AddVertexBuffer(vertexBuffer0);
201   mGeometry.AddVertexBuffer(vertexBuffer1);
202
203   mGeometry.SetType(Geometry::TRIANGLES);
204
205   mVertexBuffer = vertexBuffer0;
206   mStreamBuffer = vertexBuffer1;
207
208   // Set some initial data for streambuffer to force initialization
209   std::vector<uint8_t> data;
210   // Resize using only-non local streams
211   auto elementSize = mEmitter->GetParticleList().GetParticleDataSize(false);
212   data.resize(elementSize *
213               mEmitter->GetParticleList().GetCapacity() * 6u);
214   mStreamBuffer.SetData(data.data(), mEmitter->GetParticleList().GetCapacity() * 6u); // needed to initialize
215
216   // Sets up callback
217   mStreamBuffer.SetVertexBufferUpdateCallback(std::move(mStreamBufferUpdateCallback));
218
219   mRenderer = Renderer::New(mGeometry, mShader);
220
221   mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::CONTINUOUSLY);
222   // If no texture created, the substitute rect 2x2 texture will be used
223   if(!mTexture)
224   {
225     mTexture         = Texture::New(TextureType::TEXTURE_2D, Pixel::RGBA8888, 2u, 2u);
226     auto* pixelArray = new uint32_t[4]{
227       0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
228
229     auto pixelData = PixelData::New(reinterpret_cast<uint8_t*>(pixelArray), 16, 2, 2, Pixel::Format::RGBA8888, PixelData::DELETE_ARRAY);
230     mTexture.Upload(pixelData);
231   }
232   mTextureSet = TextureSet::New();
233   mTextureSet.SetTexture(0, mTexture);
234   mRenderer.SetTextures(mTextureSet);
235   mTextureSet.SetSampler(0, Sampler());
236
237
238   // Attach renderer to the parent actor
239   mEmitter->GetActor().AddRenderer(mRenderer);
240
241   if(mBlendingMode == BlendingMode::SCREEN)
242   {
243     if(Dali::Capabilities::IsBlendEquationSupported(Dali::DevelBlendEquation::SCREEN))
244     {
245       mEmitter->GetActor().SetProperty(Dali::DevelActor::Property::BLEND_EQUATION, Dali::DevelBlendEquation::SCREEN);
246     }
247     else // Fallback to default
248     {
249       mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
250     }
251   }
252   else
253   {
254     mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
255   }
256 }
257
258 uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
259 {
260   auto& list = GetImplementation(mEmitter->GetParticleList());
261
262   auto particleCount    = list.GetActiveParticleCount(); // active particle count
263   auto particleMaxCount = list.GetParticleCount();
264   if(!particleCount)
265   {
266     return 0;
267   }
268
269   auto streamCount = list.GetStreamCount();
270
271   auto elementSize = 0u; // elements size should be cached (it's also stride of buffer) (in bytes)
272   for(auto i = 0u; i < streamCount; ++i)
273   {
274     if(!list.IsStreamLocal(i))
275     {
276       elementSize += list.GetStreamDataTypeSize(i);
277     }
278   }
279
280   // Prepare source buffer (MUST BE OPTIMIZED TO AVOID ALLOCATING AND COPYING!!)
281   auto totalSize = particleMaxCount * elementSize * 6u;
282
283   // buffer sizes must match
284   if(totalSize != size)
285   {
286     // ASSERT here ?
287     return 0;
288   }
289
290   auto* dst = reinterpret_cast<uint8_t*>(streamData);
291
292   auto& particles = list.GetParticles();
293
294   // prepare worker threads
295   auto workerCount = GetThreadPool().GetWorkerCount();
296
297   // divide particles if over the threshold
298
299   [[maybe_unused]] bool runParallel = true;
300   if(!mEmitter->IsParallelProcessingEnabled() || particleCount < workerCount * 10) // don't run parallel if only a few particles to update
301   {
302     runParallel = false;
303   }
304   else
305   {
306     // Partial to handle
307     auto partialSize = (particleCount / workerCount);
308
309     struct UpdateTask
310     {
311       UpdateTask(Internal::ParticleRenderer& renderer, Internal::ParticleList& list, uint32_t particleStartIndex, uint32_t particleCount, void* basePtr)
312       : owner(renderer),
313         particleList(list),
314         startIndex(particleStartIndex),
315         count(particleCount),
316         ptr(reinterpret_cast<uint8_t*>(basePtr))
317       {
318       }
319
320       void Update()
321       {
322         // execute task
323         owner.UpdateParticlesTask(particleList, startIndex, count, ptr);
324       }
325
326       Internal::ParticleRenderer& owner;
327       Internal::ParticleList&     particleList;
328       uint32_t                    startIndex;
329       uint32_t                    count;
330       uint8_t*                    ptr;
331     };
332
333     std::vector<UpdateTask> tasks;
334     tasks.reserve(workerCount);
335     std::vector<Task> taskQueue;
336     auto              count = partialSize;
337
338     for(auto i = 0u; i < workerCount; ++i)
339     {
340       auto index = i * partialSize;
341       count      = partialSize;
342
343       // make sure there's no leftover particles!
344       if(i == workerCount - 1 && index + count < particleCount)
345       {
346         count = particleCount - index;
347       }
348
349       tasks.emplace_back(*this, list, index, count, streamData);
350       taskQueue.emplace_back([&t = tasks.back()](uint32_t threadId) { t.Update(); });
351     }
352
353     // Execute worker tasks
354     auto future = GetThreadPool().SubmitTasks(taskQueue, 0);
355     // wait to finish
356     future->Wait();
357   }
358
359   // less particles so run on a single thread
360   if(!runParallel)
361   {
362     for(auto& p : particles)
363     {
364       // without instancing we need to duplicate data 4 times per each quad
365       auto* particleDst = dst;
366       for(auto s = 0u; s < streamCount; ++s)
367       {
368         if(!list.IsStreamLocal(s))
369         {
370           // Pointer to stream value
371           auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
372
373           // Size of data
374           auto dataSize = list.GetStreamDataTypeSize(s);
375
376           memcpy(dst, valuePtr, dataSize);
377           dst += dataSize;
378         }
379       }
380       // Replicate data 5 more times for each vertex (GLES2)
381       memcpy(dst, particleDst, elementSize);
382       dst += elementSize;
383       memcpy(dst, particleDst, elementSize);
384       dst += elementSize;
385       memcpy(dst, particleDst, elementSize);
386       dst += elementSize;
387       memcpy(dst, particleDst, elementSize);
388       dst += elementSize;
389       memcpy(dst, particleDst, elementSize);
390       dst += elementSize;
391     }
392   }
393   return particleCount * 6u; // return number of elements to render
394 }
395
396 Renderer ParticleRenderer::GetRenderer() const
397 {
398   return mRenderer;
399 }
400
401 void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list,
402                                            uint32_t                particleStartIndex,
403                                            uint32_t                particleCount,
404                                            uint8_t*                basePtr)
405 {
406   auto& particles   = list.GetParticles();
407   auto  streamCount = list.GetStreamCount();
408   auto  elementSize = list.GetStreamElementSize(false);
409
410   // calculate begin of buffer
411   uint8_t* dst = (basePtr + (elementSize * 6u) * particleStartIndex);
412
413   auto it = particles.begin();
414   std::advance(it, particleStartIndex);
415
416   for(; particleCount; particleCount--, it++)
417   {
418     ParticleSystem::Particle& p = *it;
419     // without instancing we need to duplicate data 4 times per each quad
420     auto* particleDst = dst;
421     for(auto s = 0u; s < streamCount; ++s)
422     {
423       if(!list.IsStreamLocal(s))
424       {
425         // Pointer to stream value
426         auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
427
428         // Size of data
429         auto dataSize = list.GetStreamDataTypeSize(s);
430
431         memcpy(dst, valuePtr, dataSize);
432         dst += dataSize;
433       }
434     }
435     // Replicate data 5 more times for each vertex (GLES2)
436     memcpy(dst, particleDst, elementSize);
437     dst += elementSize;
438     memcpy(dst, particleDst, elementSize);
439     dst += elementSize;
440     memcpy(dst, particleDst, elementSize);
441     dst += elementSize;
442     memcpy(dst, particleDst, elementSize);
443     dst += elementSize;
444     memcpy(dst, particleDst, elementSize);
445     dst += elementSize;
446   }
447 }
448
449 bool ParticleRenderer::Initialize()
450 {
451   if(!mInitialized)
452   {
453     CreateShader();
454     mInitialized = true;
455     return true;
456   }
457
458   return false;
459 }
460
461 } // namespace Dali::Toolkit::ParticleSystem::Internal