Merge "DALi Version 1.0.20" into tizen
[platform/core/uifw/dali-core.git] / dali / internal / event / effects / shader-effect-impl.cpp
1 /*
2  * Copyright (c) 2014 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 // CLASS HEADER
19 #include <dali/internal/event/effects/shader-effect-impl.h>
20
21 // INTERNAL INCLUDES
22 #include <dali/public-api/math/vector2.h>
23 #include <dali/public-api/math/matrix.h>
24 #include <dali/public-api/math/matrix3.h>
25 #include <dali/public-api/shader-effects/shader-effect.h>
26 #include <dali/public-api/object/type-registry.h>
27 #include <dali/public-api/scripting/scripting.h>
28 #include <dali/internal/event/effects/shader-declarations.h>
29 #include <dali/internal/event/effects/shader-factory.h>
30 #include <dali/internal/event/images/image-impl.h>
31 #include <dali/internal/update/common/animatable-property.h>
32 #include <dali/internal/update/manager/update-manager.h>
33 #include <dali/internal/event/common/stage-impl.h>
34 #include <dali/internal/event/common/thread-local-storage.h>
35 #include <dali/internal/render/shaders/shader.h>
36 #include <dali/internal/render/shaders/uniform-meta.h>
37 #include <dali/internal/update/common/property-owner-messages.h>
38 #include <dali/internal/update/animation/scene-graph-constraint-base.h>
39 #include "dali-shaders.h"
40
41 using Dali::Internal::SceneGraph::UpdateManager;
42 using Dali::Internal::SceneGraph::UniformMeta;
43 using Dali::Internal::SceneGraph::Shader;
44 using Dali::Internal::SceneGraph::AnimatableProperty;
45 using Dali::Internal::SceneGraph::PropertyBase;
46 using Dali::Internal::SceneGraph::PropertyBase;
47 using Dali::Internal::SceneGraph::RenderQueue;
48 using std::string;
49
50 namespace Dali
51 {
52
53 const Property::Index ShaderEffect::GRID_DENSITY        = 0;
54 const Property::Index ShaderEffect::IMAGE               = 1;
55 const Property::Index ShaderEffect::PROGRAM             = 2;
56 const Property::Index ShaderEffect::GEOMETRY_HINTS      = 3;
57
58 namespace Internal
59 {
60
61 namespace
62 {
63 const PropertyDetails DEFAULT_PROPERTY_DETAILS[] =
64 {
65  // Name               Type            writable animatable constraint-input
66  { "grid-density",   Property::FLOAT,   true,    false,   false  }, // GRID_DENSITY
67  { "image",          Property::MAP,     true,    false,   false  }, // IMAGE
68  { "program",        Property::MAP,     true,    false,   false  }, // PROGRAM
69  { "geometry-hints", Property::INTEGER, true,    false,   false  }, // GEOMETRY_HINTS
70 };
71
72 const int DEFAULT_PROPERTY_COUNT = sizeof( DEFAULT_PROPERTY_DETAILS ) / sizeof( DEFAULT_PROPERTY_DETAILS[0] );
73
74 BaseHandle Create()
75 {
76   Internal::ShaderEffectPtr internal = Internal::ShaderEffect::New();
77
78   return Dali::ShaderEffect(internal.Get());
79 }
80
81 TypeRegistration mType( typeid(Dali::ShaderEffect), typeid(Dali::Handle), Create );
82
83 struct WrapperStrings
84 {
85   const char* vertexShaderPrefix;
86   const char* fragmentShaderPrefix;
87   const char* vertexShaderPostfix;
88   const char* fragmentShaderPostfix;
89 };
90
91 WrapperStrings customShaderWrappers [] =
92 {
93   {
94     CustomImagePrefixVertex, CustomImagePrefixFragment,
95     CustomImagePostfixVertex, CustomImagePostfixFragment
96   },
97   {
98     CustomTextDistanceFieldPrefixVertex, CustomTextDistanceFieldPrefixFragment,
99     CustomTextDistanceFieldPostfixVertex, CustomTextDistanceFieldPostfixFragment
100   },
101   {
102     CustomUntexturedMeshPrefixVertex, CustomUntexturedMeshPrefixFragment,
103     CustomUntexturedMeshPostfixVertex, CustomUntexturedMeshPostfixFragment
104   },
105   {
106     CustomTexturedMeshPrefixVertex, CustomTexturedMeshPrefixFragment,
107     CustomTexturedMeshPostfixVertex, CustomTexturedMeshPostfixFragment
108   }
109 };
110
111 /**
112  * Helper to wrap the program with our default pre and postfix if needed and then send it to update/render thread
113  * @param[in] effect of the shader
114  * @param[in] actualGeometryType of the shader
115  * @param[in] expectedGeometryType of the shader
116  * @param[in] vertexPrefix from application
117  * @param[in] fragmentPrefix from application
118  * @param[in] vertexBody from application
119  * @param[in] fragmentBody from application
120  * @param[in] modifiesGeometry based on flags and vertex shader
121  */
122 void WrapAndSetProgram( Internal::ShaderEffect& effect,
123                         GeometryType actualGeometryType, GeometryType expectedGeometryType,
124                         const std::string& vertexPrefix, const std::string& fragmentPrefix,
125                         const std::string& vertexBody, const std::string& fragmentBody,
126                         bool modifiesGeometry )
127 {
128   // if geometry type matches and there is some real data in the strings
129   if( ( actualGeometryType & expectedGeometryType )&&
130       ( ( vertexPrefix.length() > 0   )||
131         ( fragmentPrefix.length() > 0 )||
132         ( vertexBody.length() > 0     )||
133         ( fragmentBody.length() > 0   ) ) )
134   {
135     std::string vertexSource = vertexPrefix;
136     std::string fragmentSource = fragmentPrefix;
137
138     // create complete shader program strings for the given geometry type
139     unsigned int index = 0;
140     switch( expectedGeometryType )
141     {
142       case GEOMETRY_TYPE_IMAGE:
143       {
144         index = 0;
145         break;
146       }
147       case GEOMETRY_TYPE_TEXT:
148       {
149         index = 1;
150         break;
151       }
152       case GEOMETRY_TYPE_UNTEXTURED_MESH:
153       {
154         index = 2;
155         break;
156       }
157       case GEOMETRY_TYPE_TEXTURED_MESH:
158       {
159         index = 3;
160         break;
161       }
162       case GEOMETRY_TYPE_LAST:
163       {
164         DALI_ASSERT_DEBUG(0 && "Wrong geometry type");
165         break;
166       }
167     }
168
169     vertexSource += customShaderWrappers[index].vertexShaderPrefix;
170     // Append the custom vertex shader code if supplied, otherwise append the default
171     if ( vertexBody.length() > 0 )
172     {
173       vertexSource.append( vertexBody );
174     }
175     else
176     {
177       vertexSource.append( customShaderWrappers[index].vertexShaderPostfix );
178     }
179
180     fragmentSource += customShaderWrappers[index].fragmentShaderPrefix;
181     // Append the custom fragment shader code if supplied, otherwise append the default
182     if ( fragmentBody.length() > 0 )
183     {
184       fragmentSource.append( fragmentBody );
185     }
186     else
187     {
188       fragmentSource.append( customShaderWrappers[index].fragmentShaderPostfix );
189     }
190
191     effect.SendProgramMessage( expectedGeometryType, SHADER_SUBTYPE_ALL, vertexSource, fragmentSource, modifiesGeometry );
192   }
193 }
194
195 std::string GetShader(const std::string& field, const Property::Value& property)
196 {
197   std::string value;
198   if( property.HasKey(field) )
199   {
200     DALI_ASSERT_ALWAYS(property.GetValue(field).GetType() == Property::STRING && "Shader property is not a string" );
201
202     // we could also check here for an array of strings as convenience for json not having multi line strings
203     value = property.GetValue(field).Get<std::string>();
204   }
205
206   return value;
207 }
208
209 } // unnamed namespace
210
211 ShaderEffectPtr ShaderEffect::New( Dali::ShaderEffect::GeometryHints hints )
212 {
213   ThreadLocalStorage& tls = ThreadLocalStorage::Get();
214   UpdateManager& updateManager = tls.GetUpdateManager();
215
216   ShaderEffectPtr shaderEffect( new ShaderEffect( updateManager, hints ) );
217   shaderEffect->RegisterObject();
218
219   return shaderEffect;
220 }
221
222 ShaderEffect::ShaderEffect( UpdateManager& updateManager, Dali::ShaderEffect::GeometryHints hints )
223 : mUpdateManager( updateManager ),
224   mConnectionCount (0),
225   mGeometryHints( hints )
226 {
227   mSceneObject = new Shader( hints );
228   DALI_ASSERT_DEBUG( NULL != mSceneObject );
229
230   // Transfer shader ownership to a scene message
231   AddShaderMessage( mUpdateManager, *mSceneObject );
232 }
233
234 ShaderEffect::~ShaderEffect()
235 {
236   // Guard to allow handle destruction after Core has been destroyed
237   if ( Stage::IsInstalled() )
238   {
239     // Remove scene-object using a message to the UpdateManager
240     if( mSceneObject )
241     {
242       RemoveShaderMessage( mUpdateManager, *mSceneObject );
243     }
244     UnregisterObject();
245   }
246 }
247
248 void ShaderEffect::SetEffectImage( Dali::Image image )
249 {
250   // if images are the same, do nothing
251   if (mImage == image)
252   {
253     return;
254   }
255
256   if (mImage && mConnectionCount > 0)
257   {
258     // unset previous image
259     GetImplementation(mImage).Disconnect();
260   }
261
262   // in case image is empty this will reset our image handle
263   mImage = image;
264
265   if (!image)
266   {
267     // mSceneShader can be in a separate thread; queue a setter message
268     SetTextureIdMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, 0 );
269   }
270   else
271   {
272     // tell image that we're using it
273     if (mConnectionCount > 0)
274     {
275       GetImplementation(mImage).Connect();
276     }
277     // mSceneShader can be in a separate thread; queue a setter message
278     SetTextureIdMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, GetImplementation(mImage).GetResourceId() );
279   }
280 }
281
282 void ShaderEffect::SetUniform( const std::string& name, Property::Value value, UniformCoordinateType uniformCoordinateType )
283 {
284   // Register the property if it does not exist
285   Property::Index index = GetPropertyIndex( name );
286   if ( Property::INVALID_INDEX == index )
287   {
288     index = RegisterProperty( name, value );
289   }
290
291   SetProperty( index, value );
292
293   // RegisterProperty guarantees a positive value as index
294   DALI_ASSERT_DEBUG( static_cast<unsigned int>(index) >= CustomPropertyStartIndex() );
295   unsigned int metaIndex = index - CustomPropertyStartIndex();
296   // check if there's space in cache
297   if( mCoordinateTypes.Count() < (metaIndex + 1) )
298   {
299     mCoordinateTypes.Resize( metaIndex + 1 );
300   }
301   // only send message if the value is different than current, initial value is COORDINATE_TYPE_DEFAULT (0)
302   if( uniformCoordinateType != mCoordinateTypes[ metaIndex ] )
303   {
304     mCoordinateTypes[ metaIndex ] = uniformCoordinateType;
305     SetCoordinateTypeMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, metaIndex, uniformCoordinateType );
306   }
307 }
308
309 void ShaderEffect::AttachExtension( Dali::ShaderEffect::Extension *extension )
310 {
311   DALI_ASSERT_ALWAYS( extension != NULL && "Attaching uninitialized extension" );
312   mExtension = IntrusivePtr<Dali::ShaderEffect::Extension>( extension );
313 }
314
315 Dali::ShaderEffect::Extension& ShaderEffect::GetExtension()
316 {
317   DALI_ASSERT_ALWAYS( mExtension && "Getting uninitialized extension" );
318   return *mExtension;
319 }
320
321 const Dali::ShaderEffect::Extension& ShaderEffect::GetExtension() const
322 {
323   DALI_ASSERT_ALWAYS( mExtension && "Getting uninitialized extension" );
324   return *mExtension;
325 }
326
327 void ShaderEffect::SetPrograms( GeometryType geometryType, const string& vertexSource, const string& fragmentSource )
328 {
329   SetPrograms( geometryType, "", "", vertexSource, fragmentSource );
330 }
331
332 void ShaderEffect::SetPrograms( GeometryType geometryType,
333                                 const std::string& vertexPrefix, const std::string& fragmentPrefix,
334                                 const std::string& vertexSource, const std::string& fragmentSource )
335 {
336   bool modifiesGeometry = true;
337   // check if the vertex shader is empty (means it cannot modify geometry)
338   if( (vertexPrefix.length() == 0 )&&( vertexSource.length() == 0 ) )
339   {
340     modifiesGeometry = false;
341   }
342   // check the hint second
343   if( (mGeometryHints & Dali::ShaderEffect::HINT_DOESNT_MODIFY_GEOMETRY ) != 0 )
344   {
345     modifiesGeometry = false;
346   }
347
348   WrapAndSetProgram( *this, geometryType, GEOMETRY_TYPE_IMAGE, vertexPrefix, fragmentPrefix, vertexSource, fragmentSource, modifiesGeometry );
349   WrapAndSetProgram( *this, geometryType, GEOMETRY_TYPE_TEXT, vertexPrefix, fragmentPrefix, vertexSource, fragmentSource, modifiesGeometry );
350   WrapAndSetProgram( *this, geometryType, GEOMETRY_TYPE_TEXTURED_MESH, vertexPrefix, fragmentPrefix, vertexSource, fragmentSource, modifiesGeometry );
351   WrapAndSetProgram( *this, geometryType, GEOMETRY_TYPE_UNTEXTURED_MESH, vertexPrefix, fragmentPrefix, vertexSource, fragmentSource, modifiesGeometry );
352 }
353
354 void ShaderEffect::SendProgramMessage( GeometryType geometryType, ShaderSubTypes subType,
355                                        const string& vertexSource, const string& fragmentSource,
356                                        bool modifiesGeometry )
357 {
358   ThreadLocalStorage& tls = ThreadLocalStorage::Get();
359   ShaderFactory& shaderFactory = tls.GetShaderFactory();
360   size_t shaderHash;
361
362   ResourceTicketPtr ticket( shaderFactory.Load(vertexSource, fragmentSource, shaderHash) );
363
364   DALI_LOG_INFO( Debug::Filter::gShader, Debug::General, "ShaderEffect: SetProgram(geometryType %d subType:%d ticket.id:%d)\n", geometryType, subType, ticket->GetId() );
365
366   // Add shader program to scene-object using a message to the UpdateManager
367   SetShaderProgramMessage( mUpdateManager, *mSceneObject, geometryType, subType, ticket->GetId(), shaderHash, modifiesGeometry );
368
369   mTickets.push_back(ticket);       // add ticket to collection to keep it alive.
370 }
371
372 void ShaderEffect::Connect()
373 {
374   ++mConnectionCount;
375
376   if (mImage && mConnectionCount == 1)
377   {
378     GetImplementation(mImage).Connect();
379
380     // Image may have changed resource due to load/release policy. Ensure correct texture ID is set on scene graph object
381     SetTextureIdMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, GetImplementation(mImage).GetResourceId() );
382   }
383 }
384
385 void ShaderEffect::Disconnect()
386 {
387   DALI_ASSERT_DEBUG(mConnectionCount > 0);
388   --mConnectionCount;
389
390   if (mImage && mConnectionCount == 0)
391   {
392      GetImplementation(mImage).Disconnect();
393   }
394 }
395
396 bool ShaderEffect::IsSceneObjectRemovable() const
397 {
398   return false; // The Shader is not removed during this proxy's lifetime
399 }
400
401 unsigned int ShaderEffect::GetDefaultPropertyCount() const
402 {
403   return DEFAULT_PROPERTY_COUNT;
404 }
405
406 void ShaderEffect::GetDefaultPropertyIndices( Property::IndexContainer& indices ) const
407 {
408   indices.reserve( DEFAULT_PROPERTY_COUNT );
409
410   for ( int i = 0; i < DEFAULT_PROPERTY_COUNT; ++i )
411   {
412     indices.push_back( i );
413   }
414 }
415
416 const char* ShaderEffect::GetDefaultPropertyName(Property::Index index) const
417 {
418   if( index < DEFAULT_PROPERTY_COUNT )
419   {
420     return DEFAULT_PROPERTY_DETAILS[index].name;
421   }
422   else
423   {
424     return NULL;
425   }
426 }
427
428 Property::Index ShaderEffect::GetDefaultPropertyIndex(const std::string& name) const
429 {
430   Property::Index index = Property::INVALID_INDEX;
431
432   // Look for name in default properties
433   for( int i = 0; i < DEFAULT_PROPERTY_COUNT; ++i )
434   {
435     const Internal::PropertyDetails* property = &DEFAULT_PROPERTY_DETAILS[ i ];
436     if( 0 == strcmp( name.c_str(), property->name ) ) // dont want to convert rhs to string
437     {
438       index = i;
439       break;
440     }
441   }
442
443   return index;
444
445 }
446
447 bool ShaderEffect::IsDefaultPropertyWritable(Property::Index index) const
448 {
449   return true; // all properties are writable
450 }
451
452 bool ShaderEffect::IsDefaultPropertyAnimatable(Property::Index index) const
453 {
454   return false; // all properties are non animatable
455 }
456
457 bool ShaderEffect::IsDefaultPropertyAConstraintInput( Property::Index index ) const
458 {
459   return false; // all properties cannot be used as constraint input
460 }
461
462 Property::Type ShaderEffect::GetDefaultPropertyType(Property::Index index) const
463 {
464   if( index < DEFAULT_PROPERTY_COUNT )
465   {
466     return DEFAULT_PROPERTY_DETAILS[index].type;
467   }
468   else
469   {
470     // index out of range...return Property::NONE
471     return Property::NONE;
472   }
473 }
474
475 void ShaderEffect::SetDefaultProperty( Property::Index index, const Property::Value& propertyValue )
476 {
477   switch ( index )
478   {
479     case Dali::ShaderEffect::GRID_DENSITY:
480     {
481       SetGridDensityMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, propertyValue.Get<float>() );
482       break;
483     }
484
485     case Dali::ShaderEffect::IMAGE:
486     {
487       Dali::Image img(Scripting::NewImage( propertyValue ));
488       if(img)
489       {
490         SetEffectImage( img );
491       }
492       else
493       {
494         DALI_LOG_WARNING("Cannot create image from property value for ShaderEffect image\n");
495       }
496       break;
497     }
498
499     case Dali::ShaderEffect::PROGRAM:
500     {
501       std::string vertexPrefix   = GetShader("vertex-prefix", propertyValue);
502       std::string fragmentPrefix = GetShader("fragment-prefix", propertyValue);
503       std::string vertex         = GetShader("vertex", propertyValue);
504       std::string fragment       = GetShader("fragment", propertyValue);
505
506       GeometryType geometryType      = GEOMETRY_TYPE_IMAGE;
507
508       if( propertyValue.HasKey("geometry-type") )
509       {
510         Property::Value geometryValue  = propertyValue.GetValue("geometry-type");
511         DALI_ASSERT_ALWAYS(geometryValue.GetType() == Property::STRING && "Geometry type is not a string" );
512
513         std::string s = geometryValue.Get<std::string>();
514         if(s == "GEOMETRY_TYPE_IMAGE")
515         {
516           geometryType  = GEOMETRY_TYPE_IMAGE;
517         }
518         else if (s == "GEOMETRY_TYPE_TEXT")
519         {
520           geometryType  = GEOMETRY_TYPE_TEXT;
521         }
522         else if( s == "GEOMETRY_TYPE_UNTEXTURED_MESH")
523         {
524           geometryType  = GEOMETRY_TYPE_UNTEXTURED_MESH;
525         }
526         else if( s == "GEOMETRY_TYPE_TEXTURED_MESH")
527         {
528           geometryType  = GEOMETRY_TYPE_TEXTURED_MESH;
529         }
530         else
531         {
532           DALI_ASSERT_ALWAYS(!"Geometry type unknown" );
533         }
534       }
535       SetPrograms( geometryType, vertexPrefix, fragmentPrefix, vertex, fragment );
536       break;
537     }
538
539     case Dali::ShaderEffect::GEOMETRY_HINTS:
540     {
541       Dali::ShaderEffect::GeometryHints hint = Dali::ShaderEffect::HINT_NONE;
542       Property::Value geometryHintsValue   = propertyValue.GetValue("geometry-hints");
543
544       std::string s = geometryHintsValue.Get<std::string>();
545       if(s == "HINT_NONE")
546       {
547         hint = Dali::ShaderEffect::HINT_NONE;
548       }
549       else if(s == "HINT_GRID_X")
550       {
551         hint = Dali::ShaderEffect::HINT_GRID_X;
552       }
553       else if(s == "HINT_GRID_Y")
554       {
555         hint = Dali::ShaderEffect::HINT_GRID_Y;
556       }
557       else if(s == "HINT_GRID")
558       {
559         hint = Dali::ShaderEffect::HINT_GRID;
560       }
561       else if(s == "HINT_DEPTH_BUFFER")
562       {
563         hint = Dali::ShaderEffect::HINT_DEPTH_BUFFER;
564       }
565       else if(s == "HINT_BLENDING")
566       {
567         hint = Dali::ShaderEffect::HINT_BLENDING;
568       }
569       else if(s == "HINT_DOESNT_MODIFY_GEOMETRY")
570       {
571         hint = Dali::ShaderEffect::HINT_DOESNT_MODIFY_GEOMETRY;
572       }
573       else
574       {
575         DALI_ASSERT_ALWAYS(!"Geometry hint unknown" );
576       }
577
578       SetHintsMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, hint );
579
580       break;
581     }
582
583     default:
584     {
585       DALI_ASSERT_ALWAYS(false && "ShaderEffect property enumeration out of range"); // should not come here
586       break;
587     }
588   }
589 }
590
591 Property::Value ShaderEffect::GetDefaultProperty(Property::Index /*index*/) const
592 {
593   // none of our properties are readable so return empty
594   return Property::Value();
595 }
596
597 void ShaderEffect::InstallSceneObjectProperty( PropertyBase& newProperty, const std::string& name, unsigned int index )
598 {
599   // Warning - the property is added to the Shader object in the Update thread and the meta-data is added in the Render thread (through a secondary message)
600
601   // mSceneObject is being used in a separate thread; queue a message to add the property
602   InstallCustomPropertyMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, newProperty ); // Message takes ownership
603
604   // mSceneObject requires metadata for each custom property (uniform)
605   UniformMeta* meta = UniformMeta::New( name, newProperty, Dali::ShaderEffect::COORDINATE_TYPE_DEFAULT );
606   // mSceneObject is being used in a separate thread; queue a message to add the property
607   InstallUniformMetaMessage( mUpdateManager.GetEventToUpdate(), *mSceneObject, *meta ); // Message takes ownership
608 }
609
610 const SceneGraph::PropertyOwner* ShaderEffect::GetSceneObject() const
611 {
612   return mSceneObject;
613 }
614
615 const PropertyBase* ShaderEffect::GetSceneObjectAnimatableProperty( Property::Index index ) const
616 {
617   CustomPropertyLookup::const_iterator entry = GetCustomPropertyLookup().find( index );
618
619   DALI_ASSERT_ALWAYS( GetCustomPropertyLookup().end() != entry && "Property index is invalid" );
620
621   DALI_ASSERT_ALWAYS( entry->second.IsAnimatable() && "shader effect has only animatable properties" );
622
623   return dynamic_cast<const PropertyBase*>( entry->second.GetSceneGraphProperty() );
624 }
625
626 const PropertyInputImpl* ShaderEffect::GetSceneObjectInputProperty( Property::Index index ) const
627 {
628   CustomPropertyLookup::const_iterator entry = GetCustomPropertyLookup().find( index );
629
630   DALI_ASSERT_ALWAYS( GetCustomPropertyLookup().end() != entry && "Property index is invalid" );
631
632   DALI_ASSERT_ALWAYS( entry->second.IsAnimatable() && "shader effect has only animatable properties" );
633
634   return entry->second.GetSceneGraphProperty();
635 }
636
637 } // namespace Internal
638
639 } // namespace Dali