Updated ImageVisual to handle CPU Image Masking 64/130964/8
authorDavid Steele <david.steele@samsung.com>
Mon, 27 Mar 2017 13:24:36 +0000 (14:24 +0100)
committerDavid Steele <david.steele@samsung.com>
Wed, 7 Jun 2017 19:48:16 +0000 (20:48 +0100)
Updated ImageVisual to add a URL for an alpha mask image. If this is present
in the initialization map, then the mask is loaded and stored in CPU (it
may be used by other textures). On triggering the Async image loading in
TextureManager, the alpha mask's textureId can be passed in.

Added new state machine to TextureManager to handle loading a mask and
and an image; and to apply the mask to the image when both have loaded.

Image masks are applied in CPU side, so they are stored in CPU memory.
All other images are uploaded to GPU ( after optional mask has been applied )

Change-Id: I6cce7f62d6d6765dc6199bb25891791333533dea

automated-tests/resources/mask.png [new file with mode: 0644]
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-compare-types.h
automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp
dali-toolkit/devel-api/visuals/image-visual-properties-devel.h
dali-toolkit/internal/visuals/image/image-visual.cpp
dali-toolkit/internal/visuals/image/image-visual.h
dali-toolkit/internal/visuals/texture-manager.cpp
dali-toolkit/internal/visuals/texture-manager.h

diff --git a/automated-tests/resources/mask.png b/automated-tests/resources/mask.png
new file mode 100644 (file)
index 0000000..0032f29
Binary files /dev/null and b/automated-tests/resources/mask.png differ
index 248276e..dd6ba24 100644 (file)
@@ -173,9 +173,16 @@ inline bool CompareType<Property::Value>(Property::Value q1, Property::Value q2,
       result = CompareType<Quaternion>(a, b, epsilon);
       break;
     }
       result = CompareType<Quaternion>(a, b, epsilon);
       break;
     }
+    case Property::STRING:
+    {
+      std::string a, b;
+      q1.Get(a);
+      q2.Get(b);
+      result = (a.compare(b) == 0);
+      break;
+    }
     case Property::MATRIX:
     case Property::MATRIX3:
     case Property::MATRIX:
     case Property::MATRIX3:
-    case Property::STRING:
     case Property::ARRAY:
     case Property::MAP:
     {
     case Property::ARRAY:
     case Property::MAP:
     {
index a56df48..0e19358 100644 (file)
@@ -30,6 +30,7 @@
 #include <dali-toolkit/devel-api/visuals/image-visual-properties-devel.h>
 #include <dali-toolkit/devel-api/visual-factory/transition-data.h>
 #include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
 #include <dali-toolkit/devel-api/visuals/image-visual-properties-devel.h>
 #include <dali-toolkit/devel-api/visual-factory/transition-data.h>
 #include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
+#include <dali-toolkit/devel-api/controls/control-devel.h>
 #include <dali-toolkit/dali-toolkit.h>
 #include "dummy-control.h"
 
 #include <dali-toolkit/dali-toolkit.h>
 #include "dummy-control.h"
 
@@ -48,12 +49,13 @@ void dali_image_visual_cleanup(void)
 
 namespace
 {
 
 namespace
 {
-const char* TEST_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR  "/gallery_small-1.jpg";
+const char* TEST_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR  "/gallery-small-1.jpg";
 const char* TEST_LARGE_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR "/tbcol.png";
 const char* TEST_SMALL_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/icon-edit.png";
 const char* TEST_REMOTE_IMAGE_FILE_NAME = "https://www.tizen.org/sites/all/themes/tizen_theme/logo.png";
 const char* TEST_INVALID_FILE_NAME =  TEST_RESOURCE_DIR  "/invalid.jpg";
 const char* TEST_REMOTE_INVALID_FILE_NAME = "https://www.tizen.org/invalid.png";
 const char* TEST_LARGE_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR "/tbcol.png";
 const char* TEST_SMALL_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/icon-edit.png";
 const char* TEST_REMOTE_IMAGE_FILE_NAME = "https://www.tizen.org/sites/all/themes/tizen_theme/logo.png";
 const char* TEST_INVALID_FILE_NAME =  TEST_RESOURCE_DIR  "/invalid.jpg";
 const char* TEST_REMOTE_INVALID_FILE_NAME = "https://www.tizen.org/invalid.png";
+const char* TEST_MASK_IMAGE_FILE_NAME =  TEST_RESOURCE_DIR "/mask.png";
 }
 
 
 }
 
 
@@ -973,3 +975,107 @@ int UtcDaliImageVisualSetInvalidRemoteImage(void)
 
   END_TEST;
 }
 
   END_TEST;
 }
+
+int UtcDaliImageVisualAlphaMask(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline( "Request image visual with a Property::Map containing an Alpha mask" );
+
+  VisualFactory factory = VisualFactory::Get();
+  DALI_TEST_CHECK( factory );
+
+  Property::Map propertyMap;
+  propertyMap.Insert( Visual::Property::TYPE,  Visual::IMAGE );
+  propertyMap.Insert( ImageVisual::Property::URL,  TEST_LARGE_IMAGE_FILE_NAME );
+  propertyMap.Insert( DevelImageVisual::Property::ALPHA_MASK_URL, TEST_MASK_IMAGE_FILE_NAME );
+
+  Visual::Base visual = factory.CreateVisual( propertyMap );
+  DALI_TEST_CHECK( visual );
+
+  Property::Map testMap;
+  visual.CreatePropertyMap(testMap);
+  DALI_TEST_EQUALS(*testMap.Find(DevelImageVisual::Property::ALPHA_MASK_URL),Property::Value(TEST_MASK_IMAGE_FILE_NAME), TEST_LOCATION );
+
+  // For tesing the LoadResourceFunc is called, a big image size should be set, so the atlasing is not applied.
+  // Image with a size smaller than 512*512 will be uploaded as a part of the atlas.
+
+  TestGlAbstraction& gl = application.GetGlAbstraction();
+  TraceCallStack& textureTrace = gl.GetTextureTrace();
+  textureTrace.Enable(true);
+
+  DummyControl actor = DummyControl::New();
+  DummyControlImpl& dummyImpl = static_cast<DummyControlImpl&>(actor.GetImplementation());
+  dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual );
+
+  actor.SetSize( 200.f, 200.f );
+  DALI_TEST_EQUALS( actor.GetRendererCount(), 0u, TEST_LOCATION );
+  DALI_TEST_EQUALS( DevelControl::IsResourceReady( actor ), false, TEST_LOCATION );
+
+  Stage::GetCurrent().Add( actor );
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION );
+  DALI_TEST_EQUALS( DevelControl::IsResourceReady( actor ), true, TEST_LOCATION );
+
+  END_TEST;
+}
+
+int UtcDaliImageVisualRemoteAlphaMask(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline( "Request image visual with a Property::Map containing an Alpha mask" );
+
+  VisualFactory factory = VisualFactory::Get();
+  DALI_TEST_CHECK( factory );
+
+  const std::string MASK_IMAGE = TEST_REMOTE_IMAGE_FILE_NAME;
+
+  Property::Map propertyMap;
+  propertyMap.Insert( Visual::Property::TYPE,  Visual::IMAGE );
+  propertyMap.Insert( ImageVisual::Property::URL,  TEST_IMAGE_FILE_NAME );
+  propertyMap.Insert( "alphaMaskUrl", MASK_IMAGE );
+
+  Visual::Base visual = factory.CreateVisual( propertyMap );
+  DALI_TEST_CHECK( visual );
+
+  Property::Map testMap;
+  visual.CreatePropertyMap(testMap);
+  DALI_TEST_EQUALS(*testMap.Find(DevelImageVisual::Property::ALPHA_MASK_URL),Property::Value(MASK_IMAGE), TEST_LOCATION );
+
+  // For tesing the LoadResourceFunc is called, a big image size should be set, so the atlasing is not applied.
+  // Image with a size smaller than 512*512 will be uploaded as a part of the atlas.
+
+  TestGlAbstraction& gl = application.GetGlAbstraction();
+  TraceCallStack& textureTrace = gl.GetTextureTrace();
+  textureTrace.Enable(true);
+
+  DummyControl actor = DummyControl::New();
+  DummyControlImpl& dummyImpl = static_cast<DummyControlImpl&>(actor.GetImplementation());
+  dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual );
+  DALI_TEST_EQUALS( DevelControl::IsResourceReady( actor ), false, TEST_LOCATION );
+
+  actor.SetSize( 200.f, 200.f );
+  DALI_TEST_EQUALS( actor.GetRendererCount(), 0u, TEST_LOCATION );
+
+  Stage::GetCurrent().Add( actor );
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 2 ), true, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION );
+  DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION );
+  DALI_TEST_EQUALS( DevelControl::IsResourceReady( actor ), true, TEST_LOCATION );
+
+  END_TEST;
+}
index d2622da..0471ae2 100644 (file)
@@ -64,6 +64,18 @@ enum Type
    */
 
   ATLASING = WRAP_MODE_V + 2,
    */
 
   ATLASING = WRAP_MODE_V + 2,
+
+  /**
+   * @brief URL of a masking image
+   * @details Name "alphaMaskUrl", type Property::STRING, URL of image to apply as
+   * a mask after image loading. If set after the main URL has finished loading, this
+   * may necessitate a re-load of the main image. The alpha mask image will be scaled
+   * on load to match the size of the main image, then applied to the pixel data
+   * before uploading to GL.
+   * @note Optional.
+   */
+
+  ALPHA_MASK_URL = WRAP_MODE_V + 3,
 };
 
 } //namespace Property
 };
 
 } //namespace Property
index d50cb91..dc16aac 100644 (file)
@@ -62,6 +62,7 @@ const char * const IMAGE_DESIRED_WIDTH( "desiredWidth" );
 const char * const IMAGE_DESIRED_HEIGHT( "desiredHeight" );
 const char * const SYNCHRONOUS_LOADING( "synchronousLoading" );
 const char * const IMAGE_ATLASING("atlasing");
 const char * const IMAGE_DESIRED_HEIGHT( "desiredHeight" );
 const char * const SYNCHRONOUS_LOADING( "synchronousLoading" );
 const char * const IMAGE_ATLASING("atlasing");
+const char * const ALPHA_MASK_URL("alphaMaskUrl");
 
 // fitting modes
 DALI_ENUM_TO_STRING_TABLE_BEGIN( FITTING_MODE )
 
 // fitting modes
 DALI_ENUM_TO_STRING_TABLE_BEGIN( FITTING_MODE )
@@ -261,8 +262,10 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache,
   mPixelArea( FULL_TEXTURE_RECT ),
   mPlacementActor(),
   mImageUrl( imageUrl ),
   mPixelArea( FULL_TEXTURE_RECT ),
   mPlacementActor(),
   mImageUrl( imageUrl ),
+  mAlphaMaskUrl(),
   mDesiredSize( size ),
   mTextureId( TextureManager::INVALID_TEXTURE_ID ),
   mDesiredSize( size ),
   mTextureId( TextureManager::INVALID_TEXTURE_ID ),
+  mAlphaMaskId( TextureManager::INVALID_TEXTURE_ID ),
   mFittingMode( fittingMode ),
   mSamplingMode( samplingMode ),
   mWrapModeU( WrapMode::DEFAULT ),
   mFittingMode( fittingMode ),
   mSamplingMode( samplingMode ),
   mWrapModeU( WrapMode::DEFAULT ),
@@ -279,8 +282,10 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache, const Image& image )
   mPixelArea( FULL_TEXTURE_RECT ),
   mPlacementActor(),
   mImageUrl(),
   mPixelArea( FULL_TEXTURE_RECT ),
   mPlacementActor(),
   mImageUrl(),
+  mAlphaMaskUrl(),
   mDesiredSize(),
   mTextureId( TextureManager::INVALID_TEXTURE_ID ),
   mDesiredSize(),
   mTextureId( TextureManager::INVALID_TEXTURE_ID ),
+  mAlphaMaskId( TextureManager::INVALID_TEXTURE_ID ),
   mFittingMode( FittingMode::DEFAULT ),
   mSamplingMode( SamplingMode::DEFAULT ),
   mWrapModeU( WrapMode::DEFAULT ),
   mFittingMode( FittingMode::DEFAULT ),
   mSamplingMode( SamplingMode::DEFAULT ),
   mWrapModeU( WrapMode::DEFAULT ),
@@ -292,6 +297,11 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache, const Image& image )
 
 ImageVisual::~ImageVisual()
 {
 
 ImageVisual::~ImageVisual()
 {
+  if( mAlphaMaskId != TextureManager::INVALID_TEXTURE_ID )
+  {
+    TextureManager& textureManager = mFactoryCache.GetTextureManager();
+    textureManager.Remove( mAlphaMaskId );
+  }
 }
 
 void ImageVisual::DoSetProperties( const Property::Map& propertyMap )
 }
 
 void ImageVisual::DoSetProperties( const Property::Map& propertyMap )
@@ -342,9 +352,20 @@ void ImageVisual::DoSetProperties( const Property::Map& propertyMap )
       {
         DoSetProperty( Toolkit::DevelImageVisual::Property::ATLASING, keyValue.second );
       }
       {
         DoSetProperty( Toolkit::DevelImageVisual::Property::ATLASING, keyValue.second );
       }
+      else if ( keyValue.first == ALPHA_MASK_URL )
+      {
+        DoSetProperty( Toolkit::DevelImageVisual::Property::ALPHA_MASK_URL, keyValue.second );
+      }
     }
   }
 
     }
   }
 
+  if( mAlphaMaskUrl.IsValid() )
+  {
+    // Immediately trigger the alpha mask loading (it may just get a cached value)
+    TextureManager& textureManager = mFactoryCache.GetTextureManager();
+    mAlphaMaskId = textureManager.RequestMaskLoad( mAlphaMaskUrl );
+  }
+
   if( ( mImpl->mFlags & Impl::IS_SYNCHRONOUS_RESOURCE_LOADING ) && mImageUrl.IsValid() )
   {
     // if sync loading is required, the loading should start
   if( ( mImpl->mFlags & Impl::IS_SYNCHRONOUS_RESOURCE_LOADING ) && mImageUrl.IsValid() )
   {
     // if sync loading is required, the loading should start
@@ -451,6 +472,15 @@ void ImageVisual::DoSetProperty( Property::Index index, const Property::Value& v
       bool atlasing = false;
       mAttemptAtlasing = value.Get( atlasing );
     }
       bool atlasing = false;
       mAttemptAtlasing = value.Get( atlasing );
     }
+
+    case Toolkit::DevelImageVisual::Property::ALPHA_MASK_URL:
+    {
+      std::string alphaUrl;
+      if( value.Get( alphaUrl ) )
+      {
+        mAlphaMaskUrl = VisualUrl( alphaUrl );
+      }
+    }
   }
 }
 
   }
 }
 
@@ -651,8 +681,17 @@ TextureSet ImageVisual::CreateTextureSet( Vector4& textureRect, bool synchronous
     {
       mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED;
       TextureManager& textureManager = mFactoryCache.GetTextureManager();
     {
       mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED;
       TextureManager& textureManager = mFactoryCache.GetTextureManager();
-      mTextureId = textureManager.RequestLoad( mImageUrl, mDesiredSize, mFittingMode,
-                                               mSamplingMode, TextureManager::NO_ATLAS, this );
+      if( mAlphaMaskId == TextureManager::INVALID_TEXTURE_ID )
+      {
+        mTextureId = textureManager.RequestLoad( mImageUrl, mDesiredSize, mFittingMode,
+                                                 mSamplingMode, TextureManager::NO_ATLAS, this );
+      }
+      else
+      {
+        mTextureId = textureManager.RequestLoad( mImageUrl, mAlphaMaskId, mDesiredSize,
+                                                 mFittingMode, mSamplingMode,
+                                                 TextureManager::NO_ATLAS, this );
+      }
 
       TextureManager::LoadState loadState = textureManager.GetTextureState( mTextureId );
 
 
       TextureManager::LoadState loadState = textureManager.GetTextureState( mTextureId );
 
@@ -819,6 +858,7 @@ void ImageVisual::DoCreatePropertyMap( Property::Map& map ) const
   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV );
 
   map.Insert( Toolkit::DevelImageVisual::Property::ATLASING, mAttemptAtlasing );
   map.Insert( Toolkit::ImageVisual::Property::WRAP_MODE_V, mWrapModeV );
 
   map.Insert( Toolkit::DevelImageVisual::Property::ATLASING, mAttemptAtlasing );
+  map.Insert( Toolkit::DevelImageVisual::Property::ALPHA_MASK_URL, mAlphaMaskUrl.GetUrl() );
 }
 
 void ImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const
 }
 
 void ImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const
index 9113437..31dc79f 100644 (file)
@@ -46,13 +46,14 @@ class ImageVisual;
 typedef IntrusivePtr< ImageVisual > ImageVisualPtr;
 
 /**
 typedef IntrusivePtr< ImageVisual > ImageVisualPtr;
 
 /**
- * The visual which renders an image to the control's quad
+ * The visual which renders an image to a quad geometry
  *
  * The following properties are optional
  *
  * | %Property Name     | Type              |
  * |--------------------|-------------------|
  * | url                | STRING            |
  *
  * The following properties are optional
  *
  * | %Property Name     | Type              |
  * |--------------------|-------------------|
  * | url                | STRING            |
+ * | alphaMaskUrl       | STRING            |
  * | fittingMode        | INTEGER OR STRING |
  * | samplingMode       | INTEGER OR STRING |
  * | desiredWidth       | INTEGER           |
  * | fittingMode        | INTEGER OR STRING |
  * | samplingMode       | INTEGER OR STRING |
  * | desiredWidth       | INTEGER           |
@@ -319,9 +320,11 @@ private:
   Vector4 mPixelArea;
   WeakHandle<Actor> mPlacementActor;
   VisualUrl mImageUrl;
   Vector4 mPixelArea;
   WeakHandle<Actor> mPlacementActor;
   VisualUrl mImageUrl;
+  VisualUrl mAlphaMaskUrl;
 
   Dali::ImageDimensions mDesiredSize;
   TextureManager::TextureId mTextureId;
 
   Dali::ImageDimensions mDesiredSize;
   TextureManager::TextureId mTextureId;
+  TextureManager::TextureId mAlphaMaskId;
 
   Dali::FittingMode::Type mFittingMode:3;
   Dali::SamplingMode::Type mSamplingMode:4;
 
   Dali::FittingMode::Type mFittingMode:3;
   Dali::SamplingMode::Type mSamplingMode:4;
index 7c8098d..21fda25 100644 (file)
@@ -20,6 +20,7 @@
 
 // EXTERNAL HEADERS
 #include <dali/devel-api/common/hash.h>
 
 // EXTERNAL HEADERS
 #include <dali/devel-api/common/hash.h>
+#include <dali/devel-api/images/pixel-data-mask.h>
 #include <dali/devel-api/images/texture-set-image.h>
 #include <dali/integration-api/debug.h>
 
 #include <dali/devel-api/images/texture-set-image.h>
 #include <dali/integration-api/debug.h>
 
@@ -29,6 +30,7 @@
 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
 
 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
 
+
 namespace Dali
 {
 
 namespace Dali
 {
 
@@ -71,13 +73,46 @@ TextureManager::TextureId TextureManager::RequestLoad(
   const UseAtlas           useAtlas,
   TextureUploadObserver*   observer )
 {
   const UseAtlas           useAtlas,
   TextureUploadObserver*   observer )
 {
+  return RequestInternalLoad( url, INVALID_TEXTURE_ID, desiredSize, fittingMode, samplingMode, useAtlas, GPU_UPLOAD, observer );
+}
+
+TextureManager::TextureId TextureManager::RequestLoad(
+  const VisualUrl&         url,
+  TextureId                maskTextureId,
+  const ImageDimensions    desiredSize,
+  FittingMode::Type        fittingMode,
+  Dali::SamplingMode::Type samplingMode,
+  const UseAtlas           useAtlas,
+  TextureUploadObserver*   observer )
+{
+  return RequestInternalLoad( url, maskTextureId, desiredSize, fittingMode, samplingMode, useAtlas, GPU_UPLOAD, observer );
+}
+
+TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
+{
+  // Use the normal load procedure to get the alpha mask.
+  return RequestInternalLoad( maskUrl, INVALID_TEXTURE_ID, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, CPU, NULL );
+}
+
+
+TextureManager::TextureId TextureManager::RequestInternalLoad(
+  const VisualUrl&         url,
+  TextureId                maskTextureId,
+  const ImageDimensions    desiredSize,
+  FittingMode::Type        fittingMode,
+  Dali::SamplingMode::Type samplingMode,
+  UseAtlas                 useAtlas,
+  StorageType              storageType,
+  TextureUploadObserver*   observer )
+{
   // First check if the requested Texture is cached.
   // First check if the requested Texture is cached.
-  const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas );
+  const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
 
 
-  // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
-  int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas );
   TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
 
   TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
 
+  // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
+  int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
+
   // Check if the requested Texture exists in the cache.
   if( cacheIndex != INVALID_CACHE_INDEX )
   {
   // Check if the requested Texture exists in the cache.
   if( cacheIndex != INVALID_CACHE_INDEX )
   {
@@ -87,19 +122,25 @@ TextureManager::TextureId TextureManager::RequestLoad(
 
     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) Using cached texture @%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
   }
 
     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) Using cached texture @%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
   }
-  else
+
+  if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
   {
     // We need a new Texture.
     textureId = GenerateUniqueTextureId();
   {
     // We need a new Texture.
     textureId = GenerateUniqueTextureId();
-    mTextureInfoContainer.push_back( TextureInfo( textureId, url.GetUrl(), desiredSize, fittingMode, samplingMode, false, useAtlas, textureHash ) );
+    mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
+                                                  desiredSize, fittingMode, samplingMode,
+                                                  false, useAtlas, textureHash ) );
     cacheIndex = mTextureInfoContainer.size() - 1u;
 
     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
   }
 
   // The below code path is common whether we are using the cache or not.
     cacheIndex = mTextureInfoContainer.size() - 1u;
 
     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
   }
 
   // The below code path is common whether we are using the cache or not.
-  // The textureInfoIndex now refers to either a pre-existing cached TextureInfo, or a new TextureInfo just created.
+  // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
+  // or a new TextureInfo just created.
   TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
   TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
+  textureInfo.maskTextureId = maskTextureId;
+  textureInfo.storageType = storageType;
 
   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
                  textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
 
   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
                  textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
@@ -127,7 +168,7 @@ TextureManager::TextureId TextureManager::RequestLoad(
       {
         // The Texture has already loaded. The other observers have already been notified.
         // We need to send a "late" loaded notification for this observer.
       {
         // The Texture has already loaded. The other observers have already been notified.
         // We need to send a "late" loaded notification for this observer.
-        observer->UploadComplete( textureInfo.loadingSucceeded,
+        observer->UploadComplete( true,
                                   textureInfo.textureSet, textureInfo.useAtlas,
                                   textureInfo.atlasRect );
       }
                                   textureInfo.textureSet, textureInfo.useAtlas,
                                   textureInfo.atlasRect );
       }
@@ -141,6 +182,11 @@ TextureManager::TextureId TextureManager::RequestLoad(
       ObserveTexture( textureInfo, observer );
       break;
     }
       ObserveTexture( textureInfo, observer );
       break;
     }
+    case TextureManager::LOAD_FINISHED:
+    case TextureManager::WAITING_FOR_MASK:
+    case TextureManager::LOAD_FAILED:
+      // Loading has already completed. Do nothing.
+      break;
   }
 
   // Return the TextureId for which this Texture can now be referenced by externally.
   }
 
   // Return the TextureId for which this Texture can now be referenced by externally.
@@ -226,8 +272,6 @@ TextureSet TextureManager::GetTextureSet( TextureId textureId )
   return textureSet;
 }
 
   return textureSet;
 }
 
-
-
 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
 {
   bool success = true;
 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
 {
   bool success = true;
@@ -241,16 +285,18 @@ bool TextureManager::LoadTexture( TextureInfo& textureInfo )
       if( textureInfo.url.IsLocal() )
       {
         mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
       if( textureInfo.url.IsLocal() )
       {
         mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
-        mAsyncLocalLoadingInfoContainer.back().loadId = GetImplementation(mAsyncLocalLoader).Load(
-          textureInfo.url, textureInfo.desiredSize,
-          textureInfo.fittingMode, textureInfo.samplingMode, true );
+        mAsyncLocalLoadingInfoContainer.back().loadId =
+          GetImplementation(mAsyncLocalLoader).Load( textureInfo.url, textureInfo.desiredSize,
+                                                     textureInfo.fittingMode,
+                                                     textureInfo.samplingMode, true );
       }
       else
       {
         mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
       }
       else
       {
         mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
-        mAsyncRemoteLoadingInfoContainer.back().loadId = GetImplementation(mAsyncRemoteLoader).Load(
-          textureInfo.url, textureInfo.desiredSize,
-          textureInfo.fittingMode, textureInfo.samplingMode, true );
+        mAsyncRemoteLoadingInfoContainer.back().loadId =
+          GetImplementation(mAsyncRemoteLoader).Load( textureInfo.url, textureInfo.desiredSize,
+                                                      textureInfo.fittingMode,
+                                                      textureInfo.samplingMode, true );
       }
     }
   }
       }
     }
   }
@@ -291,15 +337,12 @@ void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingCo
       int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
       if( cacheIndex != INVALID_CACHE_INDEX )
       {
       int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
       if( cacheIndex != INVALID_CACHE_INDEX )
       {
-        // Once we have found the TextureInfo data, we call a common function used to process loaded data for both sync and async loads.
         TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
 
         DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
 
         TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
 
         DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
 
-        // Only perform atlasing if the load has not been cancelled since the request.
         if( textureInfo.loadState != CANCELLED )
         {
         if( textureInfo.loadState != CANCELLED )
         {
-          // Perform atlasing and finalize the load.
           PostLoad( textureInfo, pixelData );
         }
         else
           PostLoad( textureInfo, pixelData );
         }
         else
@@ -313,37 +356,108 @@ void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingCo
   }
 }
 
   }
 }
 
-
-bool TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
+void TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
 {
 {
-  bool success = false;
-
   // Was the load successful?
   if( pixelData && ( pixelData.GetWidth() != 0 ) && ( pixelData.GetHeight() != 0 ) )
   {
   // Was the load successful?
   if( pixelData && ( pixelData.GetWidth() != 0 ) && ( pixelData.GetHeight() != 0 ) )
   {
-    // Regardless of whether the atlasing succeeds or not, we have a valid image, so we mark it as successful.
-    success = true;
-
-    bool usingAtlas = false;
-
     // No atlas support for now
     textureInfo.useAtlas = NO_ATLAS;
 
     // No atlas support for now
     textureInfo.useAtlas = NO_ATLAS;
 
-    if( ! usingAtlas )
+    if( textureInfo.storageType == GPU_UPLOAD )
+    {
+      // If there is a mask texture ID associated with this texture, then apply the mask
+      // if it's already loaded. If it hasn't, and the mask is still loading,
+      // wait for the mask to finish loading.
+      if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
+      {
+        LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
+        if( maskLoadState == LOADING )
+        {
+          textureInfo.pixelData = pixelData; // Store the pixel data temporarily
+          textureInfo.loadState = WAITING_FOR_MASK;
+        }
+        else if( maskLoadState == LOAD_FINISHED )
+        {
+          ApplyMask( pixelData, textureInfo.maskTextureId );
+          UploadTexture( pixelData, textureInfo );
+          NotifyObservers( textureInfo, true );
+        }
+      }
+      else
+      {
+        UploadTexture( pixelData, textureInfo );
+        NotifyObservers( textureInfo, true );
+      }
+    }
+    else // currently, CPU textures are local to texture manager
     {
     {
-      DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  TextureManager::PostLoad() textureId:%d\n", textureInfo.textureId );
+      textureInfo.pixelData = pixelData; // Store the pixel data
+      textureInfo.loadState = LOAD_FINISHED;
 
 
-      Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight() );
-      texture.Upload( pixelData );
-      textureInfo.textureSet = TextureSet::New();
-      textureInfo.textureSet.SetTexture( 0u, texture );
+      // Check if there was another texture waiting for this load to complete
+      // (e.g. if this was an image mask, and its load is on a different thread)
+      CheckForWaitingTexture( textureInfo );
     }
   }
     }
   }
-
-  if( ! success )
+  else
   {
     DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
     // @todo If the load was unsuccessful, upload the broken image.
   {
     DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
     // @todo If the load was unsuccessful, upload the broken image.
+    textureInfo.loadState = LOAD_FAILED;
+    CheckForWaitingTexture( textureInfo );
+    NotifyObservers( textureInfo, false );
+  }
+}
+
+void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
+{
+  // Search the cache, checking if any texture has this texture id as a
+  // maskTextureId:
+  const unsigned int size = mTextureInfoContainer.size();
+
+  for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
+  {
+    if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
+        mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
+    {
+      TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
+      PixelData pixelData = textureInfo.pixelData;
+      textureInfo.pixelData.Reset();
+
+      if( maskTextureInfo.loadState == LOAD_FINISHED )
+      {
+        ApplyMask( pixelData, maskTextureInfo.textureId );
+        UploadTexture( pixelData, textureInfo );
+        NotifyObservers( textureInfo, true );
+      }
+      else
+      {
+        DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
+        textureInfo.loadState = LOAD_FAILED;
+        NotifyObservers( textureInfo, false );
+      }
+    }
+  }
+}
+
+void TextureManager::ApplyMask( PixelData pixelData, TextureId maskTextureId )
+{
+  int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
+  PixelData maskPixelData = mTextureInfoContainer[maskCacheIndex].pixelData;
+  Dali::ApplyMask( pixelData, maskPixelData );
+}
+
+void TextureManager::UploadTexture( PixelData pixelData, TextureInfo& textureInfo )
+{
+  if( textureInfo.useAtlas != USE_ATLAS );
+  {
+    DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
+
+    Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight() );
+    texture.Upload( pixelData );
+    textureInfo.textureSet = TextureSet::New();
+    textureInfo.textureSet.SetTexture( 0u, texture );
   }
 
   // Update the load state.
   }
 
   // Update the load state.
@@ -351,12 +465,11 @@ bool TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
   // load attempt is in progress or not.  If unsuccessful, a broken
   // image is still loaded.
   textureInfo.loadState = UPLOADED;
   // load attempt is in progress or not.  If unsuccessful, a broken
   // image is still loaded.
   textureInfo.loadState = UPLOADED;
+}
 
 
-  // We need to store the load succeeded state as if a future request to load this texture comes in,
-  // we need to re-broadcast the UploadComplete notification to that observer.
-  textureInfo.loadingSucceeded = success;
-
-  // If there is an observer: Notify the load is complete, whether successful or not:
+void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
+{
+  // If there is an observer: Notify the upload is complete
   const unsigned int observerCount = textureInfo.observerList.Count();
   for( unsigned int i = 0; i < observerCount; ++i )
   {
   const unsigned int observerCount = textureInfo.observerList.Count();
   for( unsigned int i = 0; i < observerCount; ++i )
   {
@@ -369,10 +482,9 @@ bool TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
   }
 
   textureInfo.observerList.Clear();
   }
 
   textureInfo.observerList.Clear();
-
-  return success;
 }
 
 }
 
+
 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
 {
   return mCurrentTextureId++;
 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
 {
   return mCurrentTextureId++;
@@ -399,7 +511,8 @@ TextureManager::TextureHash TextureManager::GenerateHash(
   const ImageDimensions          size,
   const FittingMode::Type        fittingMode,
   const Dali::SamplingMode::Type samplingMode,
   const ImageDimensions          size,
   const FittingMode::Type        fittingMode,
   const Dali::SamplingMode::Type samplingMode,
-  const UseAtlas                 useAtlas )
+  const UseAtlas                 useAtlas,
+  TextureId                      maskTextureId )
 {
   std::string hashTarget( url );
   const size_t urlLength = hashTarget.length();
 {
   std::string hashTarget( url );
   const size_t urlLength = hashTarget.length();
@@ -431,6 +544,20 @@ TextureManager::TextureHash TextureManager::GenerateHash(
     hashTarget[ urlLength ] = useAtlas;
   }
 
     hashTarget[ urlLength ] = useAtlas;
   }
 
+  if( maskTextureId != INVALID_TEXTURE_ID )
+  {
+    hashTarget.resize( urlLength + sizeof( TextureId ) );
+    TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
+
+    // Append the hash target to the end of the URL byte by byte:
+    // (to avoid SIGBUS / alignment issues)
+    for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
+    {
+      *hashTargetPtr++ = maskTextureId & 0xff;
+      maskTextureId >>= 8u;
+    }
+  }
+
   return Dali::CalculateHash( hashTarget );
 }
 
   return Dali::CalculateHash( hashTarget );
 }
 
@@ -440,7 +567,8 @@ int TextureManager::FindCachedTexture(
   const ImageDimensions             size,
   const FittingMode::Type           fittingMode,
   const Dali::SamplingMode::Type    samplingMode,
   const ImageDimensions             size,
   const FittingMode::Type           fittingMode,
   const Dali::SamplingMode::Type    samplingMode,
-  const bool                        useAtlas )
+  const bool                        useAtlas,
+  TextureId                         maskTextureId)
 {
   // Default to an invalid ID, in case we do not find a match.
   int cacheIndex = INVALID_CACHE_INDEX;
 {
   // Default to an invalid ID, in case we do not find a match.
   int cacheIndex = INVALID_CACHE_INDEX;
@@ -456,6 +584,7 @@ int TextureManager::FindCachedTexture(
 
       if( ( url == textureInfo.url.GetUrl() ) &&
           ( useAtlas == textureInfo.useAtlas ) &&
 
       if( ( url == textureInfo.url.GetUrl() ) &&
           ( useAtlas == textureInfo.useAtlas ) &&
+          ( maskTextureId == textureInfo.maskTextureId ) &&
           ( size == textureInfo.desiredSize ) &&
           ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
             ( fittingMode == textureInfo.fittingMode &&
           ( size == textureInfo.desiredSize ) &&
           ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
             ( fittingMode == textureInfo.fittingMode &&
index 312b12e..a650384 100644 (file)
@@ -41,6 +41,8 @@ namespace Toolkit
 namespace Internal
 {
 
 namespace Internal
 {
 
+class MaskTextureObserver;
+
 /**
  * The TextureManager provides a common Image loading API for Visuals.
  *
 /**
  * The TextureManager provides a common Image loading API for Visuals.
  *
@@ -55,12 +57,27 @@ public:
   typedef int32_t TextureId;       ///< The TextureId type. This is used as a handle to refer to a particular Texture.
   static const int INVALID_TEXTURE_ID = -1; ///< Used to represent a null TextureId or error
 
   typedef int32_t TextureId;       ///< The TextureId type. This is used as a handle to refer to a particular Texture.
   static const int INVALID_TEXTURE_ID = -1; ///< Used to represent a null TextureId or error
 
+  /**
+   * Whether the texture should be atlased or uploaded into it's own GPU texture
+   */
   enum UseAtlas
   {
     NO_ATLAS,
     USE_ATLAS
   };
 
   enum UseAtlas
   {
     NO_ATLAS,
     USE_ATLAS
   };
 
+  /**
+   * Whether the texture should be stored in CPU memory, or uploaded to a GPU texture
+   */
+  enum StorageType
+  {
+    CPU,
+    GPU_UPLOAD
+  };
+
+  /**
+   * Whether the texture should be loaded synchronously or asynchronously.
+   */
   enum LoadType
   {
     LOAD_ASYNCHRONOUSLY,
   enum LoadType
   {
     LOAD_ASYNCHRONOUSLY,
@@ -68,14 +85,17 @@ public:
   };
 
   /**
   };
 
   /**
-   * @brief The LoadState Enumeration represents the current state of a particular Textures life-cycle.
+   * @brief The LoadState Enumeration represents the current state of a particular Texture's life-cycle.
    */
   enum LoadState
   {
     NOT_STARTED,     ///< Default
     LOADING,         ///< Loading has been started, but not finished.
    */
   enum LoadState
   {
     NOT_STARTED,     ///< Default
     LOADING,         ///< Loading has been started, but not finished.
-    UPLOADED,        ///< Loaded (and ready).
+    LOAD_FINISHED,   ///< Loading has finished. (for CPU storage only)
+    WAITING_FOR_MASK,///< Loading has finished, but waiting for mask image
+    UPLOADED,        ///< Uploaded and ready. (For GPU upload only)
     CANCELLED,       ///< Removed before loading completed
     CANCELLED,       ///< Removed before loading completed
+    LOAD_FAILED      ///< Async loading failed, e.g. connection problem
   };
 
 public:
   };
 
 public:
@@ -91,7 +111,7 @@ public:
   ~TextureManager();
 
 
   ~TextureManager();
 
 
-// TextureManager Main API:
+  // TextureManager Main API:
 
   /**
    * @brief Requests an image load of the given URL.
 
   /**
    * @brief Requests an image load of the given URL.
@@ -119,6 +139,40 @@ public:
                          TextureUploadObserver*   observer );
 
   /**
                          TextureUploadObserver*   observer );
 
   /**
+   * @brief Requests an image load of the given URL, when the texture has
+   * have loaded, it will perform a CPU blend with the image mask, and upload
+   * the blend texture.
+   *
+   * The parameters are used to specify how the image is loaded.
+   * The observer has the UploadComplete method called when the load is ready.
+   *
+   * When the client has finished with the Texture, Remove() should be called.
+   *
+   * @param[in] url               The URL of the image to load
+   * @param[in] desiredSize       The size the image is likely to appear at. This can be set to 0,0 for automatic
+   * @param[in] fittingMode       The FittingMode to use
+   * @param[in] samplingMode      The SamplingMode to use
+   * @param[in] useAtlasing       Set to USE_ATLAS to attempt atlasing. If atlasing fails, the image will still be loaded, and marked successful,
+   *                              but "useAtlasing" will be set to false in the "UploadCompleted" callback from the TextureManagerUploadObserver.
+   * @param[in] observer          The client object should inherit from this and provide the "UploadCompleted" virtual.
+   *                              This is called when an image load completes (or fails).
+   * @return                      A TextureId to use as a handle to reference this Texture
+   */
+  TextureId RequestLoad( const VisualUrl&         url,
+                         TextureId                maskTextureId,
+                         const ImageDimensions    desiredSize,
+                         FittingMode::Type        fittingMode,
+                         Dali::SamplingMode::Type samplingMode,
+                         const UseAtlas           useAtlasing,
+                         TextureUploadObserver*   observer );
+
+  /**
+   * Requests a masking image to be loaded. This mask is not uploaded to GL,
+   * instead, it is stored in CPU memory, and can be used for CPU blending.
+   */
+  TextureId RequestMaskLoad( const VisualUrl& maskUrl );
+
+  /**
    * @brief Remove a Texture from the TextureManager.
    *
    * Textures are cached and therefore only the removal of the last
    * @brief Remove a Texture from the TextureManager.
    *
    * Textures are cached and therefore only the removal of the last
@@ -145,16 +199,48 @@ public:
 
 private:
 
 
 private:
 
+  /**
+   * @brief Requests an image load of the given URL, when the texture has
+   * have loaded, if there is a valid maskTextureId, it will perform a
+   * CPU blend with the mask, and upload the blend texture.
+   *
+   * The parameters are used to specify how the image is loaded.
+   * The observer has the UploadComplete method called when the load is ready.
+   *
+   * When the client has finished with the Texture, Remove() should be called.
+   *
+   * @param[in] url               The URL of the image to load
+   * @param[in] maskTextureId     The texture id of an image to use as a mask. If no mask is required, then set to INVALID_TEXTURE_ID
+   * @param[in] desiredSize       The size the image is likely to appear at. This can be set to 0,0 for automatic
+   * @param[in] fittingMode       The FittingMode to use
+   * @param[in] samplingMode      The SamplingMode to use
+   * @param[in] useAtlasing       Set to USE_ATLAS to attempt atlasing. If atlasing fails, the image will still be loaded, and marked successful,
+   *                              but "useAtlasing" will be set to false in the "UploadCompleted" callback from the TextureManagerUploadObserver.
+   * @param[in] storageType,      Whether the pixel data is stored in the cache or uploaded to the GPU
+   * @param[in] observer          The client object should inherit from this and provide the "UploadCompleted" virtual.
+   *                              This is called when an image load completes (or fails).
+   * @return                      A TextureId to use as a handle to reference this Texture
+   */
+  TextureId RequestInternalLoad(
+    const VisualUrl&         url,
+    TextureId                maskTextureId,
+    const ImageDimensions    desiredSize,
+    FittingMode::Type        fittingMode,
+    Dali::SamplingMode::Type samplingMode,
+    UseAtlas                 useAtlas,
+    StorageType              storageType,
+    TextureUploadObserver*   observer );
+
 
   typedef size_t TextureHash; ///< The type used to store the hash used for Texture caching.
 
   /**
    * @brief This struct is used to manage the life-cycle of Texture loading and caching.
 
   typedef size_t TextureHash; ///< The type used to store the hash used for Texture caching.
 
   /**
    * @brief This struct is used to manage the life-cycle of Texture loading and caching.
-   * TODO-TX: pimpl this
    */
   struct TextureInfo
   {
     TextureInfo( TextureId textureId,
    */
   struct TextureInfo
   {
     TextureInfo( TextureId textureId,
+                 TextureId maskTextureId,
                  const VisualUrl& url,
                  ImageDimensions desiredSize,
                  FittingMode::Type fittingMode,
                  const VisualUrl& url,
                  ImageDimensions desiredSize,
                  FittingMode::Type fittingMode,
@@ -167,14 +253,15 @@ private:
       useSize( desiredSize ),
       atlasRect( 0.0f, 0.0f, 1.0f, 1.0f ), // Full atlas rectangle
       textureId( textureId ),
       useSize( desiredSize ),
       atlasRect( 0.0f, 0.0f, 1.0f, 1.0f ), // Full atlas rectangle
       textureId( textureId ),
+      maskTextureId( maskTextureId ),
       hash( hash ),
       referenceCount( 1u ),
       loadState( NOT_STARTED ),
       fittingMode( fittingMode ),
       samplingMode( samplingMode ),
       hash( hash ),
       referenceCount( 1u ),
       loadState( NOT_STARTED ),
       fittingMode( fittingMode ),
       samplingMode( samplingMode ),
+      storageType( GPU_UPLOAD ),
       loadSynchronously( loadSynchronously ),
       loadSynchronously( loadSynchronously ),
-      useAtlas( useAtlas ),
-      loadingSucceeded( false )
+      useAtlas( useAtlas )
     {
     }
 
     {
     }
 
@@ -185,21 +272,22 @@ private:
 
     ObserverListType observerList; ///< Container used to store all observer clients of this Texture
     Toolkit::ImageAtlas atlas;     ///< The atlas this Texture lays within (if any)
 
     ObserverListType observerList; ///< Container used to store all observer clients of this Texture
     Toolkit::ImageAtlas atlas;     ///< The atlas this Texture lays within (if any)
-    PixelData pixelData;           ///< The PixelData holding the image data (this is used if atlasing is deferred)
+    PixelData pixelData;           ///< The PixelData holding the image data (this is used if atlasing is deferred or CPU storage is required)
     TextureSet textureSet;         ///< The TextureSet holding the Texture
     VisualUrl url;                 ///< The URL of the image
     ImageDimensions desiredSize;   ///< The size requested
     ImageDimensions useSize;       ///< The size used
     Vector4 atlasRect;             ///< The atlas rect used if atlased
     TextureId textureId;           ///< The TextureId associated with this Texture
     TextureSet textureSet;         ///< The TextureSet holding the Texture
     VisualUrl url;                 ///< The URL of the image
     ImageDimensions desiredSize;   ///< The size requested
     ImageDimensions useSize;       ///< The size used
     Vector4 atlasRect;             ///< The atlas rect used if atlased
     TextureId textureId;           ///< The TextureId associated with this Texture
+    TextureId maskTextureId;       ///< The mask TextureId to be applied on load
     TextureManager::TextureHash hash; ///< The hash used to cache this Texture
     int16_t referenceCount;        ///< The reference count of clients using this Texture
     LoadState loadState:3;         ///< The load state showing the load progress of the Texture
     FittingMode::Type fittingMode:2; ///< The requested FittingMode
     Dali::SamplingMode::Type samplingMode:3; ///< The requested SamplingMode
     TextureManager::TextureHash hash; ///< The hash used to cache this Texture
     int16_t referenceCount;        ///< The reference count of clients using this Texture
     LoadState loadState:3;         ///< The load state showing the load progress of the Texture
     FittingMode::Type fittingMode:2; ///< The requested FittingMode
     Dali::SamplingMode::Type samplingMode:3; ///< The requested SamplingMode
-    bool loadSynchronously;        ///< True if synchronous loading was requested
-    UseAtlas useAtlas; ///< USE_ATLAS if an atlas was requested. This is updated to false if atlas is not used
-    bool loadingSucceeded;         ///< True if the image was loaded successfully
+    StorageType storageType:1;     ///< CPU storage / GPU upload;
+    bool loadSynchronously:1;      ///< True if synchronous loading was requested
+    UseAtlas useAtlas:1;           ///< USE_ATLAS if an atlas was requested. This is updated to false if atlas is not used
   };
 
   // Structs:
   };
 
   // Structs:
@@ -279,11 +367,47 @@ private:
 
   /**
    * @brief Performs Post-Load steps including atlasing.
 
   /**
    * @brief Performs Post-Load steps including atlasing.
-   * @param[in]           textureInfo The struct associated with this Texture
-   * @param[in]           pixelData   The image pixelData
-   * @return                          True if successful
+   * @param[in] textureInfo The struct associated with this Texture
+   * @param[in] pixelData   The image pixelData
+   * @return    True if successful
    */
    */
-  bool PostLoad( TextureManager::TextureInfo& textureInfo, PixelData pixelData );
+  void PostLoad( TextureManager::TextureInfo& textureInfo, PixelData pixelData );
+
+  /**
+   * Check if there is a texture waiting to be masked. If there
+   * is then apply this mask and upload it.
+   * @param[in] maskTextureInfo The texture info of the mask that has just loaded.
+   */
+  void CheckForWaitingTexture( TextureInfo& maskTextureInfo );
+
+  /**
+   * Apply the mask texture to the image texture.
+   * @param[in] pixelData The image pixelData to apply the mask to
+   * @param[in] maskTextureId The texture id of the mask.
+   */
+  void ApplyMask( PixelData pixelData, TextureId maskTextureId );
+
+  /**
+   * Upload the texture specified in pixelData to the appropriate location
+   * @param[in] pixelData The image data to upload
+   * @param[in] textureInfo The texture info containing the location to
+   * store the data to.
+   */
+  void UploadTexture( PixelData pixelData, TextureInfo& textureInfo );
+
+  /**
+   * Mark the texture as complete, and inform observers
+   * @param[in] textureInfo The struct associated with this Texture
+   */
+  void UploadComplete( TextureInfo& textureInfo );
+
+  /**
+   * Notify the current observers that the texture upload is complete,
+   * then remove the observers from the list.
+   * @param[in] textureInfo The struct associated with this Texture
+   * @param[in] success If the pixel data was retrieved successfully and uploaded to GPU
+   */
+  void NotifyObservers( TextureInfo& textureInfo, bool success );
 
   /**
    * @brief Generates a new, unique TextureId
 
   /**
    * @brief Generates a new, unique TextureId
@@ -301,30 +425,42 @@ private:
 
   /**
    * @brief Generates a hash for caching based on the input parameters.
 
   /**
    * @brief Generates a hash for caching based on the input parameters.
+   * Only applies size, fitting mode andsampling mode if the size is specified.
+   * Only applies maskTextureId if it isn't INVALID_TEXTURE_ID
+   * Always applies useAtlas.
    * @param[in] url          The URL of the image to load
    * @param[in] size         The image size
    * @param[in] fittingMode  The FittingMode to use
    * @param[in] samplingMode The SamplingMode to use
    * @param[in] useAtlas     True if atlased
    * @param[in] url          The URL of the image to load
    * @param[in] size         The image size
    * @param[in] fittingMode  The FittingMode to use
    * @param[in] samplingMode The SamplingMode to use
    * @param[in] useAtlas     True if atlased
+   * @param[in] maskTextureId The masking texture id (or INVALID_TEXTURE_ID)
    * @return                 A hash of the provided data for caching.
    */
   TextureHash GenerateHash( const std::string& url, const ImageDimensions size,
                             const FittingMode::Type fittingMode,
    * @return                 A hash of the provided data for caching.
    */
   TextureHash GenerateHash( const std::string& url, const ImageDimensions size,
                             const FittingMode::Type fittingMode,
-                            const Dali::SamplingMode::Type samplingMode, const UseAtlas useAtlas );
+                            const Dali::SamplingMode::Type samplingMode, const UseAtlas useAtlas,
+                            TextureId maskTextureId );
 
   /**
    * @brief Looks up a cached texture by its hash.
    * If found, the given parameters are used to check there is no hash-collision.
 
   /**
    * @brief Looks up a cached texture by its hash.
    * If found, the given parameters are used to check there is no hash-collision.
-   * @param[in] hash The hash to look up
-   * @param[in] url          The URL of the image to load
-   * @param[in] size         The image size
-   * @param[in] fittingMode  The FittingMode to use
-   * @param[in] samplingMode The SamplingMode to use
-   * @param[in] useAtlas     True if atlased
-   * @return                 A TextureId of a cached Texture if found. Or INVALID_TEXTURE_ID if not found.
+   * @param[in] hash          The hash to look up
+   * @param[in] url           The URL of the image to load
+   * @param[in] size          The image size
+   * @param[in] fittingMode   The FittingMode to use
+   * @param[in] samplingMode  The SamplingMode to use
+   * @param[in] useAtlas      True if atlased
+   * @param[in] maskTextureId Optional texture ID to use to mask this image
+   * @return A TextureId of a cached Texture if found. Or INVALID_TEXTURE_ID if not found.
    */
    */
-  TextureManager::TextureId FindCachedTexture( const TextureManager::TextureHash hash, const std::string& url, const ImageDimensions size,
-      const FittingMode::Type fittingMode, const Dali::SamplingMode::Type samplingMode, const bool useAtlas );
+  TextureManager::TextureId FindCachedTexture(
+    const TextureManager::TextureHash hash,
+    const std::string& url,
+    const ImageDimensions size,
+    const FittingMode::Type fittingMode,
+    const Dali::SamplingMode::Type samplingMode,
+    const bool useAtlas,
+    TextureId maskTextureId );
 
 
 private:
 
 
 private: