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