From 305b42da3085e8b4682ddadebdab7661148a0cb6 Mon Sep 17 00:00:00 2001 From: David Steele Date: Mon, 27 Mar 2017 14:24:36 +0100 Subject: [PATCH 1/1] Updated ImageVisual to handle CPU Image Masking 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 | Bin 0 -> 7068 bytes .../dali-toolkit-test-utils/test-compare-types.h | 9 +- .../src/dali-toolkit/utc-Dali-ImageVisual.cpp | 108 +++++++++- .../visuals/image-visual-properties-devel.h | 12 ++ .../internal/visuals/image/image-visual.cpp | 44 ++++- dali-toolkit/internal/visuals/image/image-visual.h | 5 +- dali-toolkit/internal/visuals/texture-manager.cpp | 217 ++++++++++++++++----- dali-toolkit/internal/visuals/texture-manager.h | 184 ++++++++++++++--- 8 files changed, 506 insertions(+), 73 deletions(-) create mode 100644 automated-tests/resources/mask.png diff --git a/automated-tests/resources/mask.png b/automated-tests/resources/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..0032f291c3a8e19b8ac46efd86a2a85de88e938a GIT binary patch literal 7068 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::Value q1, Property::Value q2, result = CompareType(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::STRING: case Property::ARRAY: case Property::MAP: { diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp index a56df48..0e19358 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "dummy-control.h" @@ -48,12 +49,13 @@ void dali_image_visual_cleanup(void) 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_MASK_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/mask.png"; } @@ -973,3 +975,107 @@ int UtcDaliImageVisualSetInvalidRemoteImage(void) 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(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(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; +} diff --git a/dali-toolkit/devel-api/visuals/image-visual-properties-devel.h b/dali-toolkit/devel-api/visuals/image-visual-properties-devel.h index d2622da..0471ae2 100644 --- a/dali-toolkit/devel-api/visuals/image-visual-properties-devel.h +++ b/dali-toolkit/devel-api/visuals/image-visual-properties-devel.h @@ -64,6 +64,18 @@ enum Type */ 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 diff --git a/dali-toolkit/internal/visuals/image/image-visual.cpp b/dali-toolkit/internal/visuals/image/image-visual.cpp index d50cb91..dc16aac 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.cpp +++ b/dali-toolkit/internal/visuals/image/image-visual.cpp @@ -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 ALPHA_MASK_URL("alphaMaskUrl"); // 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 ), + mAlphaMaskUrl(), mDesiredSize( size ), mTextureId( TextureManager::INVALID_TEXTURE_ID ), + mAlphaMaskId( TextureManager::INVALID_TEXTURE_ID ), mFittingMode( fittingMode ), mSamplingMode( samplingMode ), mWrapModeU( WrapMode::DEFAULT ), @@ -279,8 +282,10 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache, const Image& image ) mPixelArea( FULL_TEXTURE_RECT ), mPlacementActor(), mImageUrl(), + mAlphaMaskUrl(), mDesiredSize(), mTextureId( TextureManager::INVALID_TEXTURE_ID ), + mAlphaMaskId( TextureManager::INVALID_TEXTURE_ID ), mFittingMode( FittingMode::DEFAULT ), mSamplingMode( SamplingMode::DEFAULT ), mWrapModeU( WrapMode::DEFAULT ), @@ -292,6 +297,11 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache, const Image& image ) ImageVisual::~ImageVisual() { + if( mAlphaMaskId != TextureManager::INVALID_TEXTURE_ID ) + { + TextureManager& textureManager = mFactoryCache.GetTextureManager(); + textureManager.Remove( mAlphaMaskId ); + } } 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 ); } + 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 @@ -451,6 +472,15 @@ void ImageVisual::DoSetProperty( Property::Index index, const Property::Value& v 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(); - 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 ); @@ -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::DevelImageVisual::Property::ALPHA_MASK_URL, mAlphaMaskUrl.GetUrl() ); } void ImageVisual::DoCreateInstancePropertyMap( Property::Map& map ) const diff --git a/dali-toolkit/internal/visuals/image/image-visual.h b/dali-toolkit/internal/visuals/image/image-visual.h index 9113437..31dc79f 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.h +++ b/dali-toolkit/internal/visuals/image/image-visual.h @@ -46,13 +46,14 @@ class ImageVisual; 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 | + * | alphaMaskUrl | STRING | * | fittingMode | INTEGER OR STRING | * | samplingMode | INTEGER OR STRING | * | desiredWidth | INTEGER | @@ -319,9 +320,11 @@ private: Vector4 mPixelArea; WeakHandle mPlacementActor; VisualUrl mImageUrl; + VisualUrl mAlphaMaskUrl; Dali::ImageDimensions mDesiredSize; TextureManager::TextureId mTextureId; + TextureManager::TextureId mAlphaMaskId; Dali::FittingMode::Type mFittingMode:3; Dali::SamplingMode::Type mSamplingMode:4; diff --git a/dali-toolkit/internal/visuals/texture-manager.cpp b/dali-toolkit/internal/visuals/texture-manager.cpp index 7c8098d..21fda25 100644 --- a/dali-toolkit/internal/visuals/texture-manager.cpp +++ b/dali-toolkit/internal/visuals/texture-manager.cpp @@ -20,6 +20,7 @@ // EXTERNAL HEADERS #include +#include #include #include @@ -29,6 +30,7 @@ #include #include + namespace Dali { @@ -71,13 +73,46 @@ TextureManager::TextureId TextureManager::RequestLoad( 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. - 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; + // 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 ) { @@ -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 ); } - else + + if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required { // 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. - // 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.maskTextureId = maskTextureId; + textureInfo.storageType = storageType; 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. - observer->UploadComplete( textureInfo.loadingSucceeded, + observer->UploadComplete( true, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect ); } @@ -141,6 +182,11 @@ TextureManager::TextureId TextureManager::RequestLoad( 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. @@ -226,8 +272,6 @@ TextureSet TextureManager::GetTextureSet( TextureId textureId ) return textureSet; } - - 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 ) ); - 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 ) ); - 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 ) { - // 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 ); - // Only perform atlasing if the load has not been cancelled since the request. if( textureInfo.loadState != CANCELLED ) { - // Perform atlasing and finalize the load. 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 ) ) { - // 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; - 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. + 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. @@ -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; +} - // 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 ) { @@ -369,10 +482,9 @@ bool TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData ) } textureInfo.observerList.Clear(); - - return success; } + 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 UseAtlas useAtlas ) + const UseAtlas useAtlas, + TextureId maskTextureId ) { std::string hashTarget( url ); const size_t urlLength = hashTarget.length(); @@ -431,6 +544,20 @@ TextureManager::TextureHash TextureManager::GenerateHash( hashTarget[ urlLength ] = useAtlas; } + if( maskTextureId != INVALID_TEXTURE_ID ) + { + hashTarget.resize( urlLength + sizeof( TextureId ) ); + TextureId* hashTargetPtr = reinterpret_cast(&( 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 ); } @@ -440,7 +567,8 @@ int TextureManager::FindCachedTexture( 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; @@ -456,6 +584,7 @@ int TextureManager::FindCachedTexture( if( ( url == textureInfo.url.GetUrl() ) && ( useAtlas == textureInfo.useAtlas ) && + ( maskTextureId == textureInfo.maskTextureId ) && ( size == textureInfo.desiredSize ) && ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) || ( fittingMode == textureInfo.fittingMode && diff --git a/dali-toolkit/internal/visuals/texture-manager.h b/dali-toolkit/internal/visuals/texture-manager.h index 312b12e..a650384 100644 --- a/dali-toolkit/internal/visuals/texture-manager.h +++ b/dali-toolkit/internal/visuals/texture-manager.h @@ -41,6 +41,8 @@ namespace Toolkit namespace Internal { +class MaskTextureObserver; + /** * 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 + /** + * Whether the texture should be atlased or uploaded into it's own GPU texture + */ 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, @@ -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. - 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 + LOAD_FAILED ///< Async loading failed, e.g. connection problem }; public: @@ -91,7 +111,7 @@ public: ~TextureManager(); -// TextureManager Main API: + // TextureManager Main API: /** * @brief Requests an image load of the given URL. @@ -119,6 +139,40 @@ public: 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 @@ -145,16 +199,48 @@ public: 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. - * TODO-TX: pimpl this */ struct TextureInfo { TextureInfo( TextureId textureId, + TextureId maskTextureId, 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 ), + maskTextureId( maskTextureId ), hash( hash ), referenceCount( 1u ), loadState( NOT_STARTED ), fittingMode( fittingMode ), samplingMode( samplingMode ), + storageType( GPU_UPLOAD ), 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) - 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 + 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 - 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: @@ -279,11 +367,47 @@ private: /** * @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 @@ -301,30 +425,42 @@ private: /** * @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] 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, - 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. - * @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: -- 2.7.4