[dali_2.3.21] Merge branch 'devel/master'
[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 BlendingMode ParticleRenderer::GetBlendingMode() const
53 {
54   return mBlendingMode;
55 }
56
57 void ParticleRenderer::CreateShader()
58 {
59   // Create shader dynamically
60   auto& list        = GetImplementation(mEmitter->GetParticleList());
61   auto  streamCount = list.GetStreamCount();
62
63   static const char* ATTR_GLSL_TYPES[] =
64     {
65       "float", "vec2", "vec3", "vec4", "int", "ivec2", "ivec3", "ivec4"};
66
67   static const Property::Type ATTR_TYPES[] =
68     {
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,
77     };
78
79   struct Vertex2D
80   {
81     Vertex2D(const Vector2& _co, const Vector2& _uv)
82     : co(_co),
83       uv(_uv)
84     {
85     }
86     Dali::Vector2 co{};
87     Dali::Vector2 uv{};
88   };
89
90   uint32_t      streamElementSize = 0u;
91   Property::Map streamAtttributes;
92
93   std::stringstream ss;
94   for(auto i = 0u; i < streamCount; ++i)
95   {
96     // Don't add local streams to the shader
97     if(!list.IsStreamLocal(i))
98     {
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;
103       char key[256];
104       if(streamName.empty())
105       {
106         snprintf(key, sizeof(key), "aStreamAttr_%d", i);
107       }
108       else
109       {
110         snprintf(key, sizeof(key), "%s", streamName.c_str());
111       }
112       streamAtttributes.Add(key, ATTR_TYPES[dataTypeIndex]);
113
114       // Add shader attribute line
115       ss << "INPUT mediump " << ATTR_GLSL_TYPES[dataTypeIndex] << " " << key << ";\n";
116     }
117   }
118
119   auto streamAttributesStr = ss.str();
120
121   /**
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
128    */
129   std::string vertexShaderCode = streamAttributesStr + std::string(
130
131                                                          "INPUT mediump vec2 aPosition;\n\
132       INPUT mediump vec2 aTexCoords;\n\
133       \n\
134       uniform mediump mat4   uMvpMatrix;\n\
135       uniform mediump vec3   uSize;\n\
136       uniform lowp vec4      uColor;\n\
137       \
138       OUTPUT mediump vec2   vTexCoord;\n\
139       OUTPUT mediump vec4 vColor;\n\
140       \n\
141       void main()\n\
142       {\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\
148       }\n");
149
150   std::string fragmentShaderCode =
151     {
152       "INPUT mediump vec2       vTexCoord;\n\
153     INPUT mediump vec4       vColor;\n\
154     uniform sampler2D sTexture;\n\
155     \n\
156     void main()\n\
157     {\n\
158       lowp vec4 col = TEXTURE(sTexture, vTexCoord) * vColor;\n\
159       if(col.a < 0.1) { discard; }\
160       fragColor = col;\n\
161     }\n"};
162
163   mShader   = Shader::New(Dali::Shader::GetVertexShaderPrefix() + vertexShaderCode, Dali::Shader::GetFragmentShaderPrefix() + fragmentShaderCode);
164   mGeometry = Geometry::New();
165
166   // Configure geometry attributes
167   Property::Map geometryMap;
168   geometryMap.Add("aPosition", Dali::Property::VECTOR2);
169   geometryMap.Add("aTexCoords", Dali::Property::VECTOR2);
170
171   // One vertex buffer with geometry
172   VertexBuffer vertexBuffer0 = VertexBuffer::New(geometryMap);
173
174   // fill the buffer entirely
175   // 2D quad
176   const static Vector2 C(0.5f, 0.5f);
177   struct Quad2D
178   {
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)};
185   } QUAD;
186
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());
191
192   // Second vertex buffer with stream data
193   VertexBuffer vertexBuffer1 = VertexBuffer::New(streamAtttributes);
194
195   /**
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
198    *
199    * For older GLES2 we need to duplicate stream data (4x more memory in case of using a quad geometry)
200    *
201    * Point-sprites may be of use in the future (problem: point sprites use screen space)
202    */
203
204   // Based on the particle system, populate buffer
205   mGeometry.AddVertexBuffer(vertexBuffer0);
206   mGeometry.AddVertexBuffer(vertexBuffer1);
207
208   mGeometry.SetType(Geometry::TRIANGLES);
209
210   mVertexBuffer = vertexBuffer0;
211   mStreamBuffer = vertexBuffer1;
212
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
220
221   // Sets up callback
222   mStreamBuffer.SetVertexBufferUpdateCallback(std::move(mStreamBufferUpdateCallback));
223
224   mRenderer = Renderer::New(mGeometry, mShader);
225
226   mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::CONTINUOUSLY);
227   // If no texture created, the substitute rect 2x2 texture will be used
228   if(!mTexture)
229   {
230     mTexture         = Texture::New(TextureType::TEXTURE_2D, Pixel::RGBA8888, 2u, 2u);
231     auto* pixelArray = new uint32_t[4]{
232       0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
233
234     auto pixelData = PixelData::New(reinterpret_cast<uint8_t*>(pixelArray), 16, 2, 2, Pixel::Format::RGBA8888, PixelData::DELETE_ARRAY);
235     mTexture.Upload(pixelData);
236   }
237   mTextureSet = TextureSet::New();
238   mTextureSet.SetTexture(0, mTexture);
239   mRenderer.SetTextures(mTextureSet);
240   mTextureSet.SetSampler(0, Sampler());
241
242   if(mBlendingMode == BlendingMode::SCREEN)
243   {
244     if(Dali::Capabilities::IsBlendEquationSupported(Dali::DevelBlendEquation::SCREEN))
245     {
246       mEmitter->GetActor().SetProperty(Dali::DevelActor::Property::BLEND_EQUATION, Dali::DevelBlendEquation::SCREEN);
247     }
248     else // Fallback to default
249     {
250       mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
251     }
252   }
253   else
254   {
255     mRenderer.SetProperty(Renderer::Property::BLEND_EQUATION_RGB, BlendEquation::ADD);
256   }
257 }
258
259 uint32_t ParticleRenderer::OnStreamBufferUpdate(void* streamData, size_t size)
260 {
261   auto& list = GetImplementation(mEmitter->GetParticleList());
262
263   auto particleCount    = list.GetActiveParticleCount(); // active particle count
264   auto particleMaxCount = list.GetParticleCount();
265   if(!particleCount)
266   {
267     return 0;
268   }
269
270   auto streamCount = list.GetStreamCount();
271
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)
274   {
275     if(!list.IsStreamLocal(i))
276     {
277       elementSize += list.GetStreamDataTypeSize(i);
278     }
279   }
280
281   // Prepare source buffer (MUST BE OPTIMIZED TO AVOID ALLOCATING AND COPYING!!)
282   auto totalSize = particleMaxCount * elementSize * 6u;
283
284   // buffer sizes must match
285   if(totalSize != size)
286   {
287     // ASSERT here ?
288     return 0;
289   }
290
291   auto* dst = reinterpret_cast<uint8_t*>(streamData);
292
293   auto& particles = list.GetParticles();
294
295   // prepare worker threads
296   auto workerCount = GetThreadPool().GetWorkerCount();
297
298   // divide particles if over the threshold
299
300   [[maybe_unused]] bool runParallel = true;
301   if(!mEmitter->IsParallelProcessingEnabled() || particleCount < workerCount * 10) // don't run parallel if only a few particles to update
302   {
303     runParallel = false;
304   }
305   else
306   {
307     // Partial to handle
308     auto partialSize = (particleCount / workerCount);
309
310     struct UpdateTask
311     {
312       UpdateTask(Internal::ParticleRenderer& renderer, Internal::ParticleList& list, uint32_t particleStartIndex, uint32_t particleCount, void* basePtr)
313       : owner(renderer),
314         particleList(list),
315         startIndex(particleStartIndex),
316         count(particleCount),
317         ptr(reinterpret_cast<uint8_t*>(basePtr))
318       {
319       }
320
321       void Update()
322       {
323         // execute task
324         owner.UpdateParticlesTask(particleList, startIndex, count, ptr);
325       }
326
327       Internal::ParticleRenderer& owner;
328       Internal::ParticleList&     particleList;
329       uint32_t                    startIndex;
330       uint32_t                    count;
331       uint8_t*                    ptr;
332     };
333
334     std::vector<UpdateTask> tasks;
335     tasks.reserve(workerCount);
336     std::vector<Task> taskQueue;
337     auto              count = partialSize;
338
339     for(auto i = 0u; i < workerCount; ++i)
340     {
341       auto index = i * partialSize;
342       count      = partialSize;
343
344       // make sure there's no leftover particles!
345       if(i == workerCount - 1 && index + count < particleCount)
346       {
347         count = particleCount - index;
348       }
349
350       tasks.emplace_back(*this, list, index, count, streamData);
351       taskQueue.emplace_back([&t = tasks.back()](uint32_t threadId) { t.Update(); });
352     }
353
354     // Execute worker tasks
355     auto future = GetThreadPool().SubmitTasks(taskQueue, 0);
356     // wait to finish
357     future->Wait();
358   }
359
360   // less particles so run on a single thread
361   if(!runParallel)
362   {
363     for(auto& p : particles)
364     {
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)
368       {
369         if(!list.IsStreamLocal(s))
370         {
371           // Pointer to stream value
372           auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
373
374           // Size of data
375           auto dataSize = list.GetStreamDataTypeSize(s);
376
377           memcpy(dst, valuePtr, dataSize);
378           dst += dataSize;
379         }
380       }
381       // Replicate data 5 more times for each vertex (GLES2)
382       memcpy(dst, particleDst, elementSize);
383       dst += elementSize;
384       memcpy(dst, particleDst, elementSize);
385       dst += elementSize;
386       memcpy(dst, particleDst, elementSize);
387       dst += elementSize;
388       memcpy(dst, particleDst, elementSize);
389       dst += elementSize;
390       memcpy(dst, particleDst, elementSize);
391       dst += elementSize;
392     }
393   }
394   return particleCount * 6u; // return number of elements to render
395 }
396
397 Renderer ParticleRenderer::GetRenderer() const
398 {
399   return mRenderer;
400 }
401
402 void ParticleRenderer::UpdateParticlesTask(Internal::ParticleList& list,
403                                            uint32_t                particleStartIndex,
404                                            uint32_t                particleCount,
405                                            uint8_t*                basePtr)
406 {
407   auto& particles   = list.GetParticles();
408   auto  streamCount = list.GetStreamCount();
409   auto  elementSize = list.GetStreamElementSize(false);
410
411   // calculate begin of buffer
412   uint8_t* dst = (basePtr + (elementSize * 6u) * particleStartIndex);
413
414   auto it = particles.begin();
415   std::advance(it, particleStartIndex);
416
417   for(; particleCount; particleCount--, it++)
418   {
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)
423     {
424       if(!list.IsStreamLocal(s))
425       {
426         // Pointer to stream value
427         auto* valuePtr = &p.GetByIndex<uint8_t*>(s);
428
429         // Size of data
430         auto dataSize = list.GetStreamDataTypeSize(s);
431
432         memcpy(dst, valuePtr, dataSize);
433         dst += dataSize;
434       }
435     }
436     // Replicate data 5 more times for each vertex (GLES2)
437     memcpy(dst, particleDst, elementSize);
438     dst += elementSize;
439     memcpy(dst, particleDst, elementSize);
440     dst += elementSize;
441     memcpy(dst, particleDst, elementSize);
442     dst += elementSize;
443     memcpy(dst, particleDst, elementSize);
444     dst += elementSize;
445     memcpy(dst, particleDst, elementSize);
446     dst += elementSize;
447   }
448 }
449
450 bool ParticleRenderer::Initialize()
451 {
452   if(!mInitialized)
453   {
454     CreateShader();
455     mInitialized = true;
456     return true;
457   }
458
459   return false;
460 }
461
462 void ParticleRenderer::PrepareToDie()
463 {
464   if(mStreamBuffer)
465   {
466     mStreamBuffer.ClearVertexBufferUpdateCallback();
467   }
468 }
469
470 } // namespace Dali::Toolkit::ParticleSystem::Internal