From 05316fdb6779dced99b135c06326ddaeea1fc2ee Mon Sep 17 00:00:00 2001 From: David Steele Date: Thu, 16 Mar 2017 20:14:38 +0000 Subject: [PATCH] Adding Async remote loading to ImageVisual Implemented a cut down version of TextureManager to handle Async loading for local and remote images, without atlasing or handling for broken image. Modified test harness to manage multiple event triggers using a round-robin semaphore test. (Required, as we now have 2 AsyncImageLoader threads in TextureManager, which each generate an EventThreadTrigger). Change-Id: I06fec6109985c7458a9135f7c3ef85a3f4503c7a Signed-off-by: David Steele --- automated-tests/resources/broken.png | Bin 0 -> 728 bytes automated-tests/resources/button-up.9.png | Bin 0 -> 897 bytes automated-tests/src/dali-toolkit/CMakeLists.txt | 1 + .../toolkit-event-thread-callback.cpp | 89 ++- .../toolkit-event-thread-callback.h | 4 +- .../src/dali-toolkit/utc-Dali-ImageAtlas.cpp | 2 +- .../src/dali-toolkit/utc-Dali-ImageVisual.cpp | 845 +++++++++++++++++++++ .../src/dali-toolkit/utc-Dali-Visual.cpp | 254 ------- .../src/dali-toolkit/utc-Dali-VisualFactory.cpp | 240 +----- dali-toolkit/internal/file.list | 2 + .../image-loader/async-image-loader-impl.cpp | 4 +- .../image-loader/async-image-loader-impl.h | 5 +- .../internal/image-loader/image-atlas-impl.cpp | 6 +- .../internal/image-loader/image-load-thread.cpp | 11 +- .../internal/image-loader/image-load-thread.h | 21 +- .../internal/visuals/image-atlas-manager.cpp | 4 +- .../internal/visuals/image/image-visual.cpp | 140 ++-- dali-toolkit/internal/visuals/image/image-visual.h | 28 +- dali-toolkit/internal/visuals/texture-manager.cpp | 505 ++++++++++++ dali-toolkit/internal/visuals/texture-manager.h | 368 +++++++++ .../internal/visuals/texture-upload-observer.cpp | 47 ++ .../internal/visuals/texture-upload-observer.h | 84 ++ .../internal/visuals/visual-factory-cache.cpp | 5 + .../internal/visuals/visual-factory-cache.h | 16 +- .../public-api/image-loader/async-image-loader.cpp | 7 +- 25 files changed, 2066 insertions(+), 622 deletions(-) create mode 100644 automated-tests/resources/broken.png create mode 100644 automated-tests/resources/button-up.9.png create mode 100644 automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp create mode 100644 dali-toolkit/internal/visuals/texture-manager.cpp create mode 100644 dali-toolkit/internal/visuals/texture-manager.h create mode 100644 dali-toolkit/internal/visuals/texture-upload-observer.cpp create mode 100644 dali-toolkit/internal/visuals/texture-upload-observer.h diff --git a/automated-tests/resources/broken.png b/automated-tests/resources/broken.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1c272cb465bc0a5915fe954a60ac1a1615308e GIT binary patch literal 728 zcmV;}0w?{6P)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!dPzhF`K%VaXqH-)OzYBVAH{r)MY zP%JO^N4MJ*e&X0{|0_lOv&!!=pU=nRu^FR|QO&4nnX08}qlH2tolcwa>2#V-r^?m= z&+{skioIMc7RTdJ*+TXfh8bV4S39FLt;9$IQenpLVJH)TGC(odOC$lQP{u-~QpwT; z$^gZnOaQuPxm>oFXh&#D%4igTl+9*c0TRth9Brupq}^_(OaL-~JVdKmpP@k|Seifv zkOyS~(2LNK>?JAz^`OjBIsqg~0Zssk*bm4961|}j1|)g`!hpnLDJ21kMSvtAahZBC zKw{jLkO3qz0mI?2-|wSj*X#A+a6sY80FqDjL&ajT)9GxtTf3-bGMVVksR5*@r02>& zta~B@$irr{iQdBjNR%}Y?VdM-1CS^M#JVQ}v&i?q%u;ed@@fAMO!4J%X}7F{AgI-9 z=kpoU3D5lz#%wmjmrp7n5$?bDdOh1c^Z7hvyoRA;9f)<$(P*R$=7jAfivUS`$%PVP z_L570n7w3#IT?G&`;JV&Uh)bMu$Sy_DY@+>y8v!`De<0|)m{p{=;pJRLIU{grQA^i zCP0!CzywI5hdnp|NmKv_AW0v$AqOM}=Y?ni$$J5Rup1lD7_gM_Hp>z4U+WPk*a98aO&1CsZDdfjn+3NQcz`-_AjMNNP0012b1^@s6UwFrL00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj58FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GP zWjp`?0|rS%K~zXfy_U;Q8c`I+iCr{t(fO;_kqYEU|#7S%9|)z#ImbUOV^5QHCYxBGx)+20n61#C9kDSkhUMx)>6 z=jW$NrP9sI;0+;}OeV*1+z|zBCR)J4!a}Sbs4fIo-x-U=Dz(5vBOXY-5Fq}+15YY8 z5Oyu%Cw-^iX`JR-tyY+voZOt5nd!I)lR;<#0;*z3SR15X3K-NX2EJTYc)A`2*3OEs ziHV6f4IxH|}4FX9ZSgIA8zz4k$*=bgy`sE`Z`6*5=m-EXY!C-Jt%mO#F@g6kVpEEzjNhA{Q zWe|tMaYBRCHSIviPjLc)z)uMT+qS#bvXSapo3<+QQ>?+k!Cw+cp-{M0gR~m_qK{%> z6P;Wygj(!cGFe<)JTF&x#|-VNZn+3zHk*MI`f6%h6o2@GQ4m~27>_@69bQy9N>C&_sSYx>JxngnVp@jf1eN}nM`(sLZPZ& zuLmPz1U@|g&T=m}*6&;@8s{_pU@|j6KHQ7VhBqS(=JWYZJV7fuoeoHLmkqpA{lG05 zz_Hc~_O;vKe0~qOl2QDi>Od#N+WCK7Ecf8VxiPw*LWE;p(rI zi!khvE=)g%PoD31cN6fBTCE1HR(pyIIOKVLcYJ(&YI=IQwRe}&mtxb$op{;*ClUNF Xd<(E{*@o$j00000NkvXXu0mjfFRGQ$ literal 0 HcmV?d00001 diff --git a/automated-tests/src/dali-toolkit/CMakeLists.txt b/automated-tests/src/dali-toolkit/CMakeLists.txt index c7a1b8b..9b8bd63 100755 --- a/automated-tests/src/dali-toolkit/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit/CMakeLists.txt @@ -18,6 +18,7 @@ SET(TC_SOURCES utc-Dali-FlexContainer.cpp utc-Dali-GaussianBlurView.cpp utc-Dali-ImageView.cpp + utc-Dali-ImageVisual.cpp utc-Dali-JsonParser.cpp utc-Dali-KeyInputFocusManager.cpp utc-Dali-PageTurnView.cpp diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp index b809b87..0f037d3 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp @@ -25,20 +25,24 @@ #include #include #include - -namespace Dali -{ +#include +#include namespace { -EventThreadCallback* gEventThreadCallback = NULL; +// Note, this is not thread safe - however, should not be using +// triggers from multiple threads - they should all be created on +// event thread. +std::vector gEventThreadCallbacks; } + +namespace Dali +{ + struct EventThreadCallback::Impl { CallbackBase* callback; - unsigned int triggeredCount; - unsigned int expectedCount; sem_t mySemaphore; }; @@ -46,38 +50,42 @@ EventThreadCallback::EventThreadCallback( CallbackBase* callback ) : mImpl( new Impl() ) { mImpl->callback = callback; - mImpl->triggeredCount = 0u; - mImpl->expectedCount = UINT_MAX; sem_init( &(mImpl->mySemaphore), 0, 0 ); - gEventThreadCallback = this; + + gEventThreadCallbacks.push_back(this); } EventThreadCallback::~EventThreadCallback() { + std::vector::iterator iter = + std::find(gEventThreadCallbacks.begin(), gEventThreadCallbacks.end(), this); + if( iter != gEventThreadCallbacks.end() ) + { + gEventThreadCallbacks.erase(iter); + } delete mImpl; } void EventThreadCallback::Trigger() { - mImpl->triggeredCount++; - if( mImpl->triggeredCount >= mImpl->expectedCount ) - { - sem_post( &(mImpl->mySemaphore) ); - } + sem_post( &(mImpl->mySemaphore) ); } -bool EventThreadCallback::WaitingForTrigger(unsigned int count, unsigned int seconds) +// returns true if timed out rather than triggered +bool EventThreadCallback::WaitingForTrigger() { - if( mImpl->triggeredCount >= count ) - { - return true; - } struct timespec now; clock_gettime( CLOCK_REALTIME, &now ); - now.tv_sec += seconds; - mImpl->expectedCount = count; + if( now.tv_nsec < 999900000 ) // 999, 900, 000 + now.tv_nsec += 100000; + else + { + now.tv_sec += 1; + now.tv_nsec = 0; + } + int error = sem_timedwait( &(mImpl->mySemaphore), &now ); - return error != 0; + return error != 0; // true if timeout } CallbackBase* EventThreadCallback::GetCallback() @@ -85,19 +93,14 @@ CallbackBase* EventThreadCallback::GetCallback() return mImpl->callback; } -EventThreadCallback* EventThreadCallback::Get() -{ - return gEventThreadCallback; } -} namespace Test { bool WaitForEventThreadTrigger( int triggerCount ) { - bool success = true; const int TEST_TIMEOUT(30); struct timespec startTime; @@ -106,23 +109,31 @@ bool WaitForEventThreadTrigger( int triggerCount ) now.tv_sec = startTime.tv_sec; now.tv_nsec = startTime.tv_nsec; - Dali::EventThreadCallback* eventTrigger = NULL; - while( eventTrigger == NULL ) + // Round robin poll of each semaphore: + while ( triggerCount > 0 ) { - eventTrigger = Dali::EventThreadCallback::Get(); + if( gEventThreadCallbacks.size() > 0 ) + { + for( std::vector::iterator iter = gEventThreadCallbacks.begin(); + iter != gEventThreadCallbacks.end(); ++iter ) + { + Dali::EventThreadCallback* eventTrigger = (*iter); + Dali::CallbackBase* callback = eventTrigger->GetCallback(); + bool timedout = eventTrigger->WaitingForTrigger(); + if( ! timedout ) + { + // Semaphore was unlocked - execute the trigger + Dali::CallbackBase::Execute( *callback ); + triggerCount--; + } + } + } clock_gettime( CLOCK_REALTIME, &now ); if( now.tv_sec - startTime.tv_sec > TEST_TIMEOUT ) { - success = false; + // Ensure we break out of the loop if elapsed time has passed break; } - usleep(10); - } - if( eventTrigger != NULL ) - { - Dali::CallbackBase* callback = eventTrigger->GetCallback(); - eventTrigger->WaitingForTrigger( triggerCount, TEST_TIMEOUT - (now.tv_sec - startTime.tv_sec) ); - Dali::CallbackBase::Execute( *callback ); } clock_gettime( CLOCK_REALTIME, &now ); @@ -130,7 +141,7 @@ bool WaitForEventThreadTrigger( int triggerCount ) { fprintf(stderr, "WaitForEventThreadTrigger took %ld seconds\n", now.tv_sec - startTime.tv_sec ); } - return success; + return triggerCount == 0; } } diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h index a0303be..1ae27d2 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h @@ -36,12 +36,10 @@ public: void Trigger(); - bool WaitingForTrigger(unsigned int count, unsigned int seconds ); + bool WaitingForTrigger(); CallbackBase* GetCallback(); - static EventThreadCallback* Get(); - private: // undefined copy constructor. diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp index f6164fb..cca026f 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp @@ -467,7 +467,7 @@ int UtcDaliImageAtlasImageView(void) application.GetPlatform().SetClosestImageSize( Vector2(100, 100) ); Stage::GetCurrent().Add( imageView3 ); - DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 3 ), true, TEST_LOCATION ); + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); application.SendNotification(); application.Render(RENDER_FRAME_INTERVAL); diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp new file mode 100644 index 0000000..fd94447 --- /dev/null +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp @@ -0,0 +1,845 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dummy-control.h" + +using namespace Dali; +using namespace Dali::Toolkit; + +void dali_image_visual_startup(void) +{ + test_return_value = TET_UNDEF; +} + +void dali_image_visual_cleanup(void) +{ + test_return_value = TET_PASS; +} + +namespace +{ +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"; +} + + +Actor CreateActorWithImageVisual(const Property::Map& map) +{ + VisualFactory factory = VisualFactory::Get(); + DummyControl actor = DummyControl::New(); + DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); + Visual::Base visual = factory.CreateVisual( map ); + DALI_TEST_CHECK( visual ); + dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual ); + actor.SetSize( 200.f, 200.f ); + DALI_TEST_EQUALS( actor.GetRendererCount(), 0u, TEST_LOCATION ); + return actor; +} + +void TestVisualRender( ToolkitTestApplication& application, + DummyControl& actor, + Visual::Base& visual, + std::size_t expectedSamplers = 0, + ImageDimensions imageDimensions = ImageDimensions(), + Integration::ResourcePointer resourcePtr = Integration::ResourcePointer()) +{ + DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual ); + + if( resourcePtr ) + { + // set the image size, for test case, this needs to be set before loading started + application.GetPlatform().SetClosestImageSize( Vector2(imageDimensions.GetWidth(), imageDimensions.GetHeight()) ); + } + + actor.SetSize( 200.f, 200.f ); + DALI_TEST_EQUALS( actor.GetRendererCount(), 0u, TEST_LOCATION ); + + Stage::GetCurrent().Add( actor ); + + application.SendNotification(); // Send messages to update + application.Render(); // process update and render + application.SendNotification(); // process any signals to event + + if( resourcePtr ) + { + DALI_TEST_EQUALS( application.GetPlatform().WasCalled(TestPlatformAbstraction::LoadResourceSynchronouslyFunc ), true, TEST_LOCATION); + } + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION ); +} + +static void TestMixColor( Visual::Base visual, Property::Index mixColorIndex, const Vector4& testColor ) +{ + Property::Map map; + visual.CreatePropertyMap(map); + Property::Value* value = map.Find( mixColorIndex ); + DALI_TEST_CHECK( value ); + Vector3 mixColor1; + DALI_TEST_CHECK( value->Get( mixColor1 ) ); + DALI_TEST_EQUALS( mixColor1, Vector3(testColor), 0.001, TEST_LOCATION ); + + value = map.Find( DevelVisual::Property::MIX_COLOR ); + DALI_TEST_CHECK( value ); + Vector4 mixColor2; + DALI_TEST_CHECK( value->Get( mixColor2 ) ); + DALI_TEST_EQUALS( mixColor2, testColor, 0.001, TEST_LOCATION ); + + value = map.Find( DevelVisual::Property::OPACITY ); + DALI_TEST_CHECK( value ); + float opacity; + DALI_TEST_CHECK( value->Get( opacity ) ); + DALI_TEST_EQUALS( opacity, testColor.a, 0.001, TEST_LOCATION ); +} + + + +int UtcDaliImageVisualPropertyMap(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request image visual with a Property::Map" ); + + 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 ); + + Visual::Base visual = factory.CreateVisual( propertyMap ); + DALI_TEST_CHECK( visual ); + + // 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 ); + + Stage::GetCurrent().Add( actor ); + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION ); + + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); + + Stage::GetCurrent().Remove( actor ); + DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); + + END_TEST; +} + + +int UtcDaliImageVisualRemoteImageLoad(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request remote image visual with a Property::Map" ); + + VisualFactory factory = VisualFactory::Get(); + DALI_TEST_CHECK( factory ); + + Property::Map propertyMap; + propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); + propertyMap.Insert( ImageVisual::Property::URL, TEST_REMOTE_IMAGE_FILE_NAME ); + + Visual::Base visual = factory.CreateVisual( propertyMap ); + DALI_TEST_CHECK( visual ); + + 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 ); + + Stage::GetCurrent().Add( actor ); + application.SendNotification(); + + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); + + Stage::GetCurrent().Remove( actor ); + DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); + + END_TEST; +} + +int UtcDaliImageVisualTextureReuse1(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request remote image visual with a Property::Map; request a second visual with the same property map - should reuse texture" ); + + Property::Map propertyMap; + propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); + propertyMap.Insert( ImageVisual::Property::URL, TEST_LARGE_IMAGE_FILE_NAME ); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + TraceCallStack& drawTrace = gl.GetDrawTrace(); + drawTrace.Enable(true); + + Actor actor = CreateActorWithImageVisual( propertyMap ); + Stage::GetCurrent().Add( actor ); + application.SendNotification(); + + // Wait for image to load + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), true, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); + DALI_TEST_EQUALS( drawTrace.FindMethod("DrawArrays"), true, TEST_LOCATION ); + textureTrace.Reset(); + drawTrace.Reset(); + + Actor actor2 = CreateActorWithImageVisual( propertyMap ); + Stage::GetCurrent().Add(actor2); + + application.SendNotification(); // Send messages to update + application.Render(); // process update and render + application.SendNotification(); // process any signals to event + + DALI_TEST_EQUALS( actor2.GetRendererCount(), 1u, TEST_LOCATION ); + + tet_infoline("Test that 2 draw calls occur with no new texture gens/binds, i.e. both\n" + "draw calls use the same texture as the previous draw call\n" ); + + DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), false, TEST_LOCATION ); + DALI_TEST_EQUALS( drawTrace.CountMethod("DrawArrays"), 2, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.CountMethod("BindTexture"), 0, TEST_LOCATION ); + + tet_infoline("Test that removing 1 actor doesn't delete the texture\n"); + + Stage::GetCurrent().Remove( actor ); + application.SendNotification(); + application.Render(); + + DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); + DALI_TEST_EQUALS( textureTrace.CountMethod("DeleteTextures"), 0, TEST_LOCATION ); + + tet_infoline("Test that removing last actor does delete the texture\n"); + + Stage::GetCurrent().Remove( actor2 ); + application.SendNotification(); + application.Render(); + + DALI_TEST_CHECK( actor2.GetRendererCount() == 0u ); + DALI_TEST_EQUALS( textureTrace.CountMethod("DeleteTextures"), 1, TEST_LOCATION ); + + END_TEST; +} + + +int UtcDaliImageVisualTextureReuse2(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request remote image visual with a Property::Map; request a second visual with the same url but different property map - should create new texture" ); + + Property::Map propertyMap; + propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); + propertyMap.Insert( ImageVisual::Property::URL, TEST_REMOTE_IMAGE_FILE_NAME ); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + TraceCallStack& drawTrace = gl.GetDrawTrace(); + drawTrace.Enable(true); + + Actor actor = CreateActorWithImageVisual( propertyMap ); + Stage::GetCurrent().Add( actor ); + application.SendNotification(); + + // Wait for image to load + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), true, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); + DALI_TEST_EQUALS( drawTrace.FindMethod("DrawArrays"), true, TEST_LOCATION ); + textureTrace.Reset(); + drawTrace.Reset(); + + propertyMap.Insert( ImageVisual::Property::SAMPLING_MODE, Dali::SamplingMode::NEAREST ); + propertyMap.Insert( ImageVisual::Property::DESIRED_WIDTH, 100 ); + propertyMap.Insert( ImageVisual::Property::DESIRED_HEIGHT, 100 ); + Actor actor2 = CreateActorWithImageVisual( propertyMap ); + Stage::GetCurrent().Add(actor2); + + application.SendNotification(); + + // Wait for image to load + DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION ); + + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS( actor2.GetRendererCount(), 1u, TEST_LOCATION ); + + tet_infoline("Test that 2 draw calls occur with 1 new texture gen/bind, i.e. both " + "renderers are using different textures\n" ); + + DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), true, TEST_LOCATION ); + DALI_TEST_EQUALS( drawTrace.CountMethod("DrawArrays"), 2, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.CountMethod("BindTexture"), 2, TEST_LOCATION ); + + tet_infoline("Test that removing 1 actor deletes it's texture\n"); + + Stage::GetCurrent().Remove( actor ); + application.SendNotification(); + application.Render(); + + DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); + DALI_TEST_EQUALS( textureTrace.CountMethod("DeleteTextures"), 1, TEST_LOCATION ); + + tet_infoline("Test that removing last actor deletes it's texture\n"); + + Stage::GetCurrent().Remove( actor2 ); + application.SendNotification(); + application.Render(); + + DALI_TEST_CHECK( actor2.GetRendererCount() == 0u ); + DALI_TEST_EQUALS( textureTrace.CountMethod("DeleteTextures"), 2, TEST_LOCATION ); + + END_TEST; +} + + +int UtcDaliImageVisualImageHandle(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request image visual with an image handle" ); + + VisualFactory factory = VisualFactory::Get(); + DALI_TEST_CHECK( factory ); + + Image image = ResourceImage::New(TEST_IMAGE_FILE_NAME); + Visual::Base visual = factory.CreateVisual( image ); + + // 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. + + const int width=512; + const int height=513; + + Integration::Bitmap* bitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD ); + bitmap->GetPackedPixelsProfile()->ReserveBuffer( Pixel::RGBA8888, width, height,width, height ); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + DummyControl actor = DummyControl::New(); + TestVisualRender( application, actor, visual, 1u, + ImageDimensions(width, height), + Integration::ResourcePointer(bitmap) ); + + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); + END_TEST; +} + +int UtcDaliImageVisualCustomWrapModePixelArea(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request image visual with a Property::Map, test custom wrap mode and pixel area with atlasing" ); + + VisualFactory factory = VisualFactory::Get(); + DALI_TEST_CHECK( factory ); + + // Test wrap mode with atlasing. Image with a size smaller than 512*512 will be uploaded as a part of the atlas. + const int width=34; + const int height=34; + const Vector4 pixelArea(-0.5f, -0.5f, 2.f, 2.f); + + Property::Map propertyMap; + propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); + propertyMap.Insert( ImageVisual::Property::URL, TEST_SMALL_IMAGE_FILE_NAME ); + propertyMap.Insert( ImageVisual::Property::DESIRED_WIDTH, width ); + propertyMap.Insert( ImageVisual::Property::DESIRED_HEIGHT, height ); + propertyMap.Insert( ImageVisual::Property::SYNCHRONOUS_LOADING, true ); + propertyMap.Insert( ImageVisual::Property::PIXEL_AREA, pixelArea ); + propertyMap.Insert( ImageVisual::Property::WRAP_MODE_U, WrapMode::MIRRORED_REPEAT ); + propertyMap.Insert( ImageVisual::Property::WRAP_MODE_V, WrapMode::REPEAT ); + propertyMap.Insert( DevelImageVisual::Property::ATLASING, true ); + + Visual::Base visual = factory.CreateVisual( propertyMap ); + DALI_TEST_CHECK( visual ); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + TraceCallStack& texParameterTrace = gl.GetTexParameterTrace(); + texParameterTrace.Enable( true ); + + DummyControl actor = DummyControl::New(); + DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual ); + actor.SetSize(2000, 2000); + actor.SetParentOrigin(ParentOrigin::CENTER); + Stage::GetCurrent().Add( actor ); + + // loading started + application.SendNotification(); + application.Render(); + + BitmapLoader loader = BitmapLoader::GetLatestCreated(); + DALI_TEST_CHECK( loader ); + loader.WaitForLoading();// waiting until the image to be loaded + DALI_TEST_CHECK( loader.IsLoaded() ); + + DALI_TEST_CHECK( actor.GetRendererCount() == 1u ); + + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); + + // WITH atlasing, the wrapping is handled manually in shader, so the following gl function should not be called + std::stringstream out; + out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_S << ", " << GL_MIRRORED_REPEAT; + DALI_TEST_CHECK( !texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); + out.str(""); + out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_T << ", " << GL_REPEAT; + DALI_TEST_CHECK( !texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); + + // test the uniforms which used to handle the wrap mode + Renderer renderer = actor.GetRendererAt( 0u ); + DALI_TEST_CHECK( renderer ); + + Property::Value pixelAreaValue = renderer.GetProperty( renderer.GetPropertyIndex( "pixelArea" ) ); + DALI_TEST_EQUALS( pixelAreaValue.Get(), pixelArea, TEST_LOCATION ); + Vector4 pixelAreaUniform; + DALI_TEST_CHECK( gl.GetUniformValue( "pixelArea", pixelAreaUniform ) ); + DALI_TEST_EQUALS( pixelArea, pixelAreaUniform, Math::MACHINE_EPSILON_100, TEST_LOCATION ); + + Property::Value wrapModeValue = renderer.GetProperty( renderer.GetPropertyIndex( "wrapMode" ) ); + Vector2 wrapMode( WrapMode::MIRRORED_REPEAT-1, WrapMode::REPEAT-1 ); + DALI_TEST_EQUALS( wrapModeValue.Get(), wrapMode, TEST_LOCATION ); + Vector2 wrapModeUniform; + DALI_TEST_CHECK( gl.GetUniformValue( "wrapMode", wrapModeUniform ) ); + DALI_TEST_EQUALS( wrapMode, wrapModeUniform, Math::MACHINE_EPSILON_100, TEST_LOCATION ); + + actor.Unparent( ); + DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); + + END_TEST; +} + +int UtcDaliImageVisualCustomWrapModeNoAtlas(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request image visual with a Property::Map, test custom wrap mode and pixel area without atlasing" ); + + VisualFactory factory = VisualFactory::Get(); + DALI_TEST_CHECK( factory ); + + // Test wrap mode without atlasing. Image with a size bigger than 512*512 will NOT be uploaded as a part of the atlas. + const int width=600; + const int height=600; + const Vector4 pixelArea(-0.5f, -0.5f, 2.f, 2.f); + + Property::Map propertyMap; + propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); + propertyMap.Insert( ImageVisual::Property::URL, TEST_LARGE_IMAGE_FILE_NAME ); + propertyMap.Insert( ImageVisual::Property::DESIRED_WIDTH, width ); + propertyMap.Insert( ImageVisual::Property::DESIRED_HEIGHT, height ); + propertyMap.Insert( ImageVisual::Property::SYNCHRONOUS_LOADING, true ); + propertyMap.Insert( ImageVisual::Property::PIXEL_AREA, pixelArea ); + propertyMap.Insert( ImageVisual::Property::WRAP_MODE_U, WrapMode::MIRRORED_REPEAT ); + propertyMap.Insert( ImageVisual::Property::WRAP_MODE_V, WrapMode::REPEAT ); + + Visual::Base visual = factory.CreateVisual( propertyMap ); + DALI_TEST_CHECK( visual ); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + TraceCallStack& texParameterTrace = gl.GetTexParameterTrace(); + texParameterTrace.Enable( true ); + + DummyControl actor = DummyControl::New(); + DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual ); + actor.SetSize(2000, 2000); + actor.SetParentOrigin(ParentOrigin::CENTER); + Stage::GetCurrent().Add( actor ); + + // loading started + application.SendNotification(); + application.Render(); + + BitmapLoader loader = BitmapLoader::GetLatestCreated(); + DALI_TEST_CHECK( loader ); + loader.WaitForLoading();// waiting until the image to be loaded + DALI_TEST_CHECK( loader.IsLoaded() ); + + DALI_TEST_CHECK( actor.GetRendererCount() == 1u ); + + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); + + // WITHOUT atlasing, the wrapping is handled by setting gl texture parameters + std::stringstream out; + out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_S << ", " << GL_MIRRORED_REPEAT; + DALI_TEST_CHECK( texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); + out.str(""); + out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_T << ", " << GL_REPEAT; + DALI_TEST_CHECK( texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); + + // test the uniforms which used to handle the wrap mode + Renderer renderer = actor.GetRendererAt( 0u ); + DALI_TEST_CHECK( renderer ); + + Property::Value pixelAreaValue = renderer.GetProperty( renderer.GetPropertyIndex( "pixelArea" ) ); + DALI_TEST_EQUALS( pixelAreaValue.Get(), pixelArea, TEST_LOCATION ); + Vector4 pixelAreaUniform; + DALI_TEST_CHECK( gl.GetUniformValue( "pixelArea", pixelAreaUniform ) ); + DALI_TEST_EQUALS( pixelArea, pixelAreaUniform, Math::MACHINE_EPSILON_100, TEST_LOCATION ); + + Property::Index wrapModeIndex = renderer.GetPropertyIndex( "wrapMode" ); + DALI_TEST_CHECK(wrapModeIndex == Property::INVALID_INDEX); + + actor.Unparent(); + DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); + + END_TEST; +} + +int UtcDaliImageVisualAnimateMixColor(void) +{ + ToolkitTestApplication application; + tet_infoline( "Animate mix color" ); + + application.GetPlatform().SetClosestImageSize( Vector2(100, 100) ); + + VisualFactory factory = VisualFactory::Get(); + Property::Map propertyMap; + propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE); + propertyMap.Insert(ImageVisual::Property::URL, TEST_IMAGE_FILE_NAME ); + propertyMap.Insert("mixColor", Color::BLUE); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); + Visual::Base visual = factory.CreateVisual( propertyMap ); + + DummyControl actor = DummyControl::New(true); + Impl::DummyControl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual ); + + actor.SetSize(2000, 2000); + actor.SetParentOrigin(ParentOrigin::CENTER); + actor.SetColor(Color::BLACK); + Stage::GetCurrent().Add(actor); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION); + + Renderer renderer = actor.GetRendererAt(0); + Property::Index index = DevelHandle::GetPropertyIndex( renderer, DevelVisual::Property::MIX_COLOR ); + Property::Value blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); + DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::AUTO, TEST_LOCATION ); + + tet_infoline("Test that the renderer has the mixColor property"); + DALI_TEST_CHECK( index != Property::INVALID_INDEX ); + + const Vector4 TARGET_MIX_COLOR( 1.0f, 0.0f, 0.0f, 0.5f ); + + Property::Map map; + map["target"] = "testVisual"; + map["property"] = "mixColor"; + map["initialValue"] = Color::MAGENTA; + map["targetValue"] = TARGET_MIX_COLOR; + map["animator"] = Property::Map() + .Add("alphaFunction", "LINEAR") + .Add("timePeriod", Property::Map() + .Add("delay", 0.0f) + .Add("duration", 4.0f)); + + Dali::Toolkit::TransitionData transition = TransitionData::New( map ); + + Animation animation = dummyImpl.CreateTransition( transition ); + + blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); + DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); + + animation.AnimateTo( Property(actor, Actor::Property::COLOR), Color::WHITE ); + animation.Play(); + + application.SendNotification(); + application.Render(0); // Ensure animation starts + application.Render(2000u); // Halfway point + Vector4 testColor(1.0f, 0.0f, 0.5f, 0.75f ); + + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("uColor", Vector4(0.5f, 0.5f, 0.5f, 1.0f )), true, TEST_LOCATION ); + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("mixColor", Vector3(testColor)), true, TEST_LOCATION ); + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", testColor.a), true, TEST_LOCATION ); + + application.Render(2000u); // Halfway point between blue and white + + DALI_TEST_EQUALS( actor.GetCurrentColor(), Color::WHITE, TEST_LOCATION ); + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("uColor", Color::WHITE ), true, TEST_LOCATION ); + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("mixColor", Vector3(TARGET_MIX_COLOR)), true, TEST_LOCATION ); + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", TARGET_MIX_COLOR.a), true, TEST_LOCATION ); + + TestMixColor( visual, DevelVisual::Property::MIX_COLOR, TARGET_MIX_COLOR ); + + blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); + DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); + + END_TEST; +} + +int UtcDaliImageVisualAnimateOpacity(void) +{ + ToolkitTestApplication application; + tet_infoline( "Animate image visual opacity" ); + + application.GetPlatform().SetClosestImageSize( Vector2(100, 100) ); + + VisualFactory factory = VisualFactory::Get(); + Property::Map propertyMap; + propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE); + propertyMap.Insert(ImageVisual::Property::URL, TEST_IMAGE_FILE_NAME ); + propertyMap.Insert("opacity", 0.5f); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); + Visual::Base visual = factory.CreateVisual( propertyMap ); + + DummyControl actor = DummyControl::New(true); + Impl::DummyControl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual ); + + actor.SetSize(2000, 2000); + actor.SetParentOrigin(ParentOrigin::CENTER); + actor.SetColor(Color::BLACK); + Stage::GetCurrent().Add(actor); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION); + + Renderer renderer = actor.GetRendererAt(0); + tet_infoline("Test that the renderer has the opacity property"); + Property::Index index = DevelHandle::GetPropertyIndex( renderer, DevelVisual::Property::OPACITY ); + DALI_TEST_CHECK( index != Property::INVALID_INDEX ); + + + Property::Value blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); + DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); + + { + tet_infoline( "Test that the opacity can be increased to full via animation, and that the blend mode is set appropriately at the start and end of the animation." ); + + Property::Map map; + map["target"] = "testVisual"; + map["property"] = "opacity"; + map["targetValue"] = 1.0f; + map["animator"] = Property::Map() + .Add("alphaFunction", "LINEAR") + .Add("timePeriod", Property::Map() + .Add("delay", 0.0f) + .Add("duration", 4.0f)); + + Dali::Toolkit::TransitionData transition = TransitionData::New( map ); + Animation animation = dummyImpl.CreateTransition( transition ); + animation.Play(); + + application.SendNotification(); + application.Render(0); // Ensure animation starts + application.Render(2000u); // Halfway point through animation + application.SendNotification(); // Handle any signals + + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 0.75f), true, TEST_LOCATION ); + + application.Render(2001u); // end + application.SendNotification(); // ensure animation finished signal is sent + + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 1.0f), true, TEST_LOCATION ); + + blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); + DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::AUTO, TEST_LOCATION ); + } + + + { + tet_infoline( "Test that the opacity can be reduced via animation, and that the blend mode is set appropriately at the start and end of the animation." ); + + Property::Map map; + map["target"] = "testVisual"; + map["property"] = DevelVisual::Property::OPACITY; + map["targetValue"] = 0.1f; + map["animator"] = Property::Map() + .Add("alphaFunction", "LINEAR") + .Add("timePeriod", Property::Map() + .Add("delay", 0.0f) + .Add("duration", 4.0f)); + + Dali::Toolkit::TransitionData transition = TransitionData::New( map ); + Animation animation = dummyImpl.CreateTransition( transition ); + animation.Play(); + + blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); + DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); + + application.SendNotification(); + application.Render(0); // Ensure animation starts + application.Render(2000u); // Halfway point + application.SendNotification(); + + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 0.55f), true, TEST_LOCATION ); + + application.Render(2016u); // end + application.SendNotification(); + + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 0.1f), true, TEST_LOCATION ); + + blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); + DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); + } + + + END_TEST; +} + +int UtcDaliImageVisualAnimatePixelArea(void) +{ + ToolkitTestApplication application; + tet_infoline( "ImageVisual animate pixel area" ); + + application.GetPlatform().SetClosestImageSize( Vector2(100, 100) ); + + VisualFactory factory = VisualFactory::Get(); + Property::Map propertyMap; + propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE); + propertyMap.Insert(ImageVisual::Property::URL, TEST_IMAGE_FILE_NAME ); + propertyMap.Insert("mixColor", Color::BLUE); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); + Visual::Base visual = factory.CreateVisual( propertyMap ); + + DummyControl actor = DummyControl::New(true); + Impl::DummyControl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual ); + + actor.SetSize(2000, 2000); + actor.SetParentOrigin(ParentOrigin::CENTER); + actor.SetColor(Color::BLACK); + Stage::GetCurrent().Add(actor); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION); + + Renderer renderer = actor.GetRendererAt(0); + Property::Index index = DevelHandle::GetPropertyIndex( renderer, DevelVisual::Property::MIX_COLOR ); + + tet_infoline("Test that the renderer has the mixColor property"); + DALI_TEST_CHECK( index != Property::INVALID_INDEX ); + + // TransitionData only takes string keys + Property::Map map; + map["target"] = "testVisual"; + map["property"] = "pixelArea"; + map["initialValue"] = Vector4( 0,0,0,1 ); + map["targetValue"] = Vector4( 0,0,1,1 ); // Animate width from zero to full + map["animator"] = Property::Map() + .Add("alphaFunction", "LINEAR") + .Add("timePeriod", Property::Map() + .Add("delay", 0.0f) + .Add("duration", 4.0f)); + + Dali::Toolkit::TransitionData transition = TransitionData::New( map ); + + Animation animation = dummyImpl.CreateTransition( transition ); + animation.AnimateTo( Property(actor, Actor::Property::COLOR), Color::WHITE ); + animation.Play(); + + application.SendNotification(); + application.Render(0); // Ensure animation starts + application.Render(2000u); // Halfway point + + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("pixelArea", Vector4(0.0f, 0.0f, 0.5f, 1.0f )), true, TEST_LOCATION ); + + application.Render(2000u); // End of animation + + DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("pixelArea", Vector4( 0.0f, 0.0f, 1.0f, 1.0f )), true, TEST_LOCATION ); + + END_TEST; +} + +int UtcDaliImageVisualTextureCancelRemoteLoad(void) +{ + ToolkitTestApplication application; + tet_infoline( "Request remote image visual, then destroy visual to cancel load" ); + + Property::Map propertyMap; + propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); + propertyMap.Insert( ImageVisual::Property::URL, TEST_REMOTE_IMAGE_FILE_NAME ); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + TraceCallStack& drawTrace = gl.GetDrawTrace(); + drawTrace.Enable(true); + + Actor actor = CreateActorWithImageVisual( propertyMap ); + Stage::GetCurrent().Add( actor ); + application.SendNotification(); + + Stage::GetCurrent().Remove( actor ); + application.SendNotification(); + + DALI_TEST_EQUALS( actor.GetRendererCount(), 0u, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.FindMethod("GenTextures"), false, TEST_LOCATION ); + DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), false, TEST_LOCATION ); + DALI_TEST_EQUALS( drawTrace.FindMethod("DrawArrays"), false, TEST_LOCATION ); + + END_TEST; +} diff --git a/automated-tests/src/dali-toolkit/utc-Dali-Visual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-Visual.cpp index 9e39e36..80429f419b 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-Visual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-Visual.cpp @@ -1371,260 +1371,6 @@ int UtcDaliVisualAnimatePrimitiveVisual(void) END_TEST; } -int UtcDaliVisualAnimateImageVisualMixColor(void) -{ - ToolkitTestApplication application; - tet_infoline( "UtcDaliAnimateImageVisual mix color" ); - - application.GetPlatform().SetClosestImageSize( Vector2(100, 100) ); - - VisualFactory factory = VisualFactory::Get(); - Property::Map propertyMap; - propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE); - propertyMap.Insert(ImageVisual::Property::URL, TEST_IMAGE_FILE_NAME ); - propertyMap.Insert("mixColor", Color::BLUE); - propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); - Visual::Base visual = factory.CreateVisual( propertyMap ); - - DummyControl actor = DummyControl::New(true); - Impl::DummyControl& dummyImpl = static_cast(actor.GetImplementation()); - dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual ); - - actor.SetSize(2000, 2000); - actor.SetParentOrigin(ParentOrigin::CENTER); - actor.SetColor(Color::BLACK); - Stage::GetCurrent().Add(actor); - - DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION); - - Renderer renderer = actor.GetRendererAt(0); - Property::Index index = DevelHandle::GetPropertyIndex( renderer, DevelVisual::Property::MIX_COLOR ); - Property::Value blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); - DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::AUTO, TEST_LOCATION ); - - tet_infoline("Test that the renderer has the mixColor property"); - DALI_TEST_CHECK( index != Property::INVALID_INDEX ); - - const Vector4 TARGET_MIX_COLOR( 1.0f, 0.0f, 0.0f, 0.5f ); - - Property::Map map; - map["target"] = "testVisual"; - map["property"] = "mixColor"; - map["initialValue"] = Color::MAGENTA; - map["targetValue"] = TARGET_MIX_COLOR; - map["animator"] = Property::Map() - .Add("alphaFunction", "LINEAR") - .Add("timePeriod", Property::Map() - .Add("delay", 0.0f) - .Add("duration", 4.0f)); - - Dali::Toolkit::TransitionData transition = TransitionData::New( map ); - - Animation animation = dummyImpl.CreateTransition( transition ); - - blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); - DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); - - animation.AnimateTo( Property(actor, Actor::Property::COLOR), Color::WHITE ); - animation.Play(); - - application.SendNotification(); - application.Render(0); - application.Render(2000u); // halfway point - Vector4 testColor(1.0f, 0.0f, 0.5f, 0.75f ); - - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("uColor", Vector4(0.5f, 0.5f, 0.5f, 1.0f )), true, TEST_LOCATION ); - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("mixColor", Vector3(testColor)), true, TEST_LOCATION ); - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", testColor.a), true, TEST_LOCATION ); - - application.Render(2000u); // halfway point between blue and white - - DALI_TEST_EQUALS( actor.GetCurrentColor(), Color::WHITE, TEST_LOCATION ); - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("uColor", Color::WHITE ), true, TEST_LOCATION ); - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("mixColor", Vector3(TARGET_MIX_COLOR)), true, TEST_LOCATION ); - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", TARGET_MIX_COLOR.a), true, TEST_LOCATION ); - - TestMixColor( visual, DevelVisual::Property::MIX_COLOR, TARGET_MIX_COLOR ); - - blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); - DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); - - END_TEST; -} - - -int UtcDaliVisualAnimateImageVisualOpacity(void) -{ - ToolkitTestApplication application; - tet_infoline( "UtcDaliAnimateImageVisual mix color" ); - - application.GetPlatform().SetClosestImageSize( Vector2(100, 100) ); - - VisualFactory factory = VisualFactory::Get(); - Property::Map propertyMap; - propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE); - propertyMap.Insert(ImageVisual::Property::URL, TEST_IMAGE_FILE_NAME ); - propertyMap.Insert("opacity", 0.5f); - propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); - Visual::Base visual = factory.CreateVisual( propertyMap ); - - DummyControl actor = DummyControl::New(true); - Impl::DummyControl& dummyImpl = static_cast(actor.GetImplementation()); - dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual ); - - actor.SetSize(2000, 2000); - actor.SetParentOrigin(ParentOrigin::CENTER); - actor.SetColor(Color::BLACK); - Stage::GetCurrent().Add(actor); - - DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION); - - Renderer renderer = actor.GetRendererAt(0); - tet_infoline("Test that the renderer has the opacity property"); - Property::Index index = DevelHandle::GetPropertyIndex( renderer, DevelVisual::Property::OPACITY ); - DALI_TEST_CHECK( index != Property::INVALID_INDEX ); - - - Property::Value blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); - DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); - - { - tet_infoline( "Test that the opacity can be increased to full via animation, and that the blend mode is set appropriately at the start and end of the animation." ); - - Property::Map map; - map["target"] = "testVisual"; - map["property"] = "opacity"; - map["targetValue"] = 1.0f; - map["animator"] = Property::Map() - .Add("alphaFunction", "LINEAR") - .Add("timePeriod", Property::Map() - .Add("delay", 0.0f) - .Add("duration", 4.0f)); - - Dali::Toolkit::TransitionData transition = TransitionData::New( map ); - Animation animation = dummyImpl.CreateTransition( transition ); - animation.Play(); - - application.SendNotification(); - application.Render(0); - application.Render(2000u); // halfway point - application.SendNotification(); - - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 0.75f), true, TEST_LOCATION ); - - application.Render(2001u); // end - application.SendNotification(); - - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 1.0f), true, TEST_LOCATION ); - - blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); - DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::AUTO, TEST_LOCATION ); - } - - - { - tet_infoline( "Test that the opacity can be reduced via animation, and that the blend mode is set appropriately at the start and end of the animation." ); - - Property::Map map; - map["target"] = "testVisual"; - map["property"] = DevelVisual::Property::OPACITY; - map["targetValue"] = 0.1f; - map["animator"] = Property::Map() - .Add("alphaFunction", "LINEAR") - .Add("timePeriod", Property::Map() - .Add("delay", 0.0f) - .Add("duration", 4.0f)); - - Dali::Toolkit::TransitionData transition = TransitionData::New( map ); - Animation animation = dummyImpl.CreateTransition( transition ); - animation.Play(); - - blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); - DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); - - application.SendNotification(); - application.Render(0); - application.Render(2000u); // halfway point - application.SendNotification(); - - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 0.55f), true, TEST_LOCATION ); - - application.Render(2016u); // end - application.SendNotification(); - - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("opacity", 0.1f), true, TEST_LOCATION ); - - blendModeValue = renderer.GetProperty( Renderer::Property::BLEND_MODE ); - DALI_TEST_EQUALS( blendModeValue.Get(), (int)BlendMode::ON, TEST_LOCATION ); - } - - - END_TEST; -} - -int UtcDaliVisualAnimateImageVisualPixelArea(void) -{ - ToolkitTestApplication application; - tet_infoline( "UtcDaliAnimateImageVisual pixel area" ); - - application.GetPlatform().SetClosestImageSize( Vector2(100, 100) ); - - VisualFactory factory = VisualFactory::Get(); - Property::Map propertyMap; - propertyMap.Insert(Visual::Property::TYPE, Visual::IMAGE); - propertyMap.Insert(ImageVisual::Property::URL, TEST_IMAGE_FILE_NAME ); - propertyMap.Insert("mixColor", Color::BLUE); - propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); - Visual::Base visual = factory.CreateVisual( propertyMap ); - - DummyControl actor = DummyControl::New(true); - Impl::DummyControl& dummyImpl = static_cast(actor.GetImplementation()); - dummyImpl.RegisterVisual( DummyControl::Property::TEST_VISUAL, visual ); - - actor.SetSize(2000, 2000); - actor.SetParentOrigin(ParentOrigin::CENTER); - actor.SetColor(Color::BLACK); - Stage::GetCurrent().Add(actor); - - DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION); - - Renderer renderer = actor.GetRendererAt(0); - Property::Index index = DevelHandle::GetPropertyIndex( renderer, DevelVisual::Property::MIX_COLOR ); - - tet_infoline("Test that the renderer has the mixColor property"); - DALI_TEST_CHECK( index != Property::INVALID_INDEX ); - - // TransitionData only takes string keys - Property::Map map; - map["target"] = "testVisual"; - map["property"] = "pixelArea"; - map["initialValue"] = Vector4( 0,0,0,1 ); - map["targetValue"] = Vector4( 0,0,1,1 ); // Animate width from zero to full - map["animator"] = Property::Map() - .Add("alphaFunction", "LINEAR") - .Add("timePeriod", Property::Map() - .Add("delay", 0.0f) - .Add("duration", 4.0f)); - - Dali::Toolkit::TransitionData transition = TransitionData::New( map ); - - Animation animation = dummyImpl.CreateTransition( transition ); - animation.AnimateTo( Property(actor, Actor::Property::COLOR), Color::WHITE ); - animation.Play(); - - application.SendNotification(); - application.Render(0); - application.Render(2000u); // halfway point - - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("pixelArea", Vector4(0.0f, 0.0f, 0.5f, 1.0f )), true, TEST_LOCATION ); - - application.Render(2000u); - - DALI_TEST_EQUALS( application.GetGlAbstraction().CheckUniformValue("pixelArea", Vector4( 0.0f, 0.0f, 1.0f, 1.0f )), true, TEST_LOCATION ); - - END_TEST; -} - int UtcDaliVisualWireframeVisual(void) { diff --git a/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp b/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp index 4966804..acf3192 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp @@ -38,8 +38,7 @@ namespace { typedef NinePatchImage::StretchRanges StretchRanges; -const char* TEST_IMAGE_FILE_NAME = "gallery_image_01.jpg"; -const char* TEST_NPATCH_FILE_NAME = "gallery_image_01.9.png"; +const char* TEST_NPATCH_FILE_NAME = TEST_RESOURCE_DIR "/button-up-1.9.png"; const char* TEST_SVG_FILE_NAME = TEST_RESOURCE_DIR "/svg1.svg"; const char* TEST_OBJ_FILE_NAME = TEST_RESOURCE_DIR "/Cube.obj"; const char* TEST_MTL_FILE_NAME = TEST_RESOURCE_DIR "/ToyRobot-Metal.mtl"; @@ -51,8 +50,7 @@ const char* TEST_GIF_FILE_NAME = TEST_RESOURCE_DIR "/anim.gif"; // resolution: 34*34, pixel format: RGBA8888 static const char* gImage_34_RGBA = TEST_RESOURCE_DIR "/icon-edit.png"; -// resolution: 600*600, pixel format: RGB888 -static const char* gImage_600_RGB = TEST_RESOURCE_DIR "/test-image-600.jpg"; + Property::Map DefaultTransform() { @@ -592,243 +590,9 @@ int UtcDaliVisualFactoryDefaultOffsetsGradientVisual(void) END_TEST; } -int UtcDaliVisualFactoryGetImageVisual1(void) -{ - ToolkitTestApplication application; - tet_infoline( "UtcDaliVisualFactoryGetImageVisual1: Request image visual with a Property::Map" ); - - VisualFactory factory = VisualFactory::Get(); - DALI_TEST_CHECK( factory ); - - Property::Map propertyMap; - propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); - propertyMap.Insert( ImageVisual::Property::URL, TEST_IMAGE_FILE_NAME ); - - Visual::Base visual = factory.CreateVisual( propertyMap ); - DALI_TEST_CHECK( visual ); - - // 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. - - const int width=512; - const int height=513; - TestGlAbstraction& gl = application.GetGlAbstraction(); - TraceCallStack& textureTrace = gl.GetTextureTrace(); - textureTrace.Enable(true); - - Integration::Bitmap* bitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD ); - bitmap->GetPackedPixelsProfile()->ReserveBuffer( Pixel::RGBA8888, width, height,width, height ); - - DummyControl actor = DummyControl::New(); - TestVisualRender( application, actor, visual, 1u, - ImageDimensions(width, height), - Integration::ResourcePointer( bitmap ) ); - - DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); - - Stage::GetCurrent().Remove( actor ); - DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); - - END_TEST; -} - -int UtcDaliVisualFactoryGetImageVisual2(void) -{ - ToolkitTestApplication application; - tet_infoline( "UtcDaliVisualFactoryGetImageVisual2: Request image visual with an image handle" ); - VisualFactory factory = VisualFactory::Get(); - DALI_TEST_CHECK( factory ); - - Image image = ResourceImage::New(TEST_IMAGE_FILE_NAME); - Visual::Base visual = factory.CreateVisual( image ); - - // 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. - - const int width=512; - const int height=513; - - Integration::Bitmap* bitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD ); - bitmap->GetPackedPixelsProfile()->ReserveBuffer( Pixel::RGBA8888, width, height,width, height ); - TestGlAbstraction& gl = application.GetGlAbstraction(); - TraceCallStack& textureTrace = gl.GetTextureTrace(); - textureTrace.Enable(true); - DummyControl actor = DummyControl::New(); - TestVisualRender( application, actor, visual, 1u, - ImageDimensions(width, height), - Integration::ResourcePointer(bitmap) ); - - DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); - END_TEST; -} - -int UtcDaliVisualFactoryGetImageVisual3(void) -{ - ToolkitTestApplication application; - tet_infoline( "UtcDaliVisualFactoryGetImageVisual3: Request image visual with a Property::Map, test custom wrap mode and pixel area with atlasing" ); - - VisualFactory factory = VisualFactory::Get(); - DALI_TEST_CHECK( factory ); - - // Test wrap mode with atlasing. Image with a size smaller than 512*512 will be uploaded as a part of the atlas. - const int width=34; - const int height=34; - const Vector4 pixelArea(-0.5f, -0.5f, 2.f, 2.f); - - Property::Map propertyMap; - propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); - propertyMap.Insert( ImageVisual::Property::URL, gImage_34_RGBA ); - propertyMap.Insert( ImageVisual::Property::DESIRED_WIDTH, width ); - propertyMap.Insert( ImageVisual::Property::DESIRED_HEIGHT, height ); - propertyMap.Insert( ImageVisual::Property::SYNCHRONOUS_LOADING, true ); - propertyMap.Insert( ImageVisual::Property::PIXEL_AREA, pixelArea ); - propertyMap.Insert( ImageVisual::Property::WRAP_MODE_U, WrapMode::MIRRORED_REPEAT ); - propertyMap.Insert( ImageVisual::Property::WRAP_MODE_V, WrapMode::REPEAT ); - propertyMap.Insert( DevelImageVisual::Property::ATLASING, true ); - - Visual::Base visual = factory.CreateVisual( propertyMap ); - DALI_TEST_CHECK( visual ); - - TestGlAbstraction& gl = application.GetGlAbstraction(); - TraceCallStack& textureTrace = gl.GetTextureTrace(); - textureTrace.Enable(true); - TraceCallStack& texParameterTrace = gl.GetTexParameterTrace(); - texParameterTrace.Enable( true ); - - DummyControl actor = DummyControl::New(); - DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); - dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual ); - actor.SetSize(2000, 2000); - actor.SetParentOrigin(ParentOrigin::CENTER); - Stage::GetCurrent().Add( actor ); - - // loading started - application.SendNotification(); - application.Render(); - application.Render(); - application.SendNotification(); - BitmapLoader loader = BitmapLoader::GetLatestCreated(); - DALI_TEST_CHECK( loader ); - loader.WaitForLoading();// waiting until the image to be loaded - DALI_TEST_CHECK( loader.IsLoaded() ); - - DALI_TEST_CHECK( actor.GetRendererCount() == 1u ); - - DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); - - // WITH atlasing, the wrapping is handled manually in shader, so the following gl function should not be called - std::stringstream out; - out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_S << ", " << GL_MIRRORED_REPEAT; - DALI_TEST_CHECK( !texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); - out.str(""); - out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_T << ", " << GL_REPEAT; - DALI_TEST_CHECK( !texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); - - // test the uniforms which used to handle the wrap mode - Renderer renderer = actor.GetRendererAt( 0u ); - DALI_TEST_CHECK( renderer ); - - Property::Value pixelAreaValue = renderer.GetProperty( renderer.GetPropertyIndex( "pixelArea" ) ); - DALI_TEST_EQUALS( pixelAreaValue.Get(), pixelArea, TEST_LOCATION ); - Vector4 pixelAreaUniform; - DALI_TEST_CHECK( gl.GetUniformValue( "pixelArea", pixelAreaUniform ) ); - DALI_TEST_EQUALS( pixelArea, pixelAreaUniform, Math::MACHINE_EPSILON_100, TEST_LOCATION ); - - Property::Value wrapModeValue = renderer.GetProperty( renderer.GetPropertyIndex( "wrapMode" ) ); - Vector2 wrapMode( WrapMode::MIRRORED_REPEAT-1, WrapMode::REPEAT-1 ); - DALI_TEST_EQUALS( wrapModeValue.Get(), wrapMode, TEST_LOCATION ); - Vector2 wrapModeUniform; - DALI_TEST_CHECK( gl.GetUniformValue( "wrapMode", wrapModeUniform ) ); - DALI_TEST_EQUALS( wrapMode, wrapModeUniform, Math::MACHINE_EPSILON_100, TEST_LOCATION ); - - actor.Unparent( ); - DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); - - END_TEST; -} - -int UtcDaliVisualFactoryGetImageVisual4(void) -{ - ToolkitTestApplication application; - tet_infoline( "UtcDaliVisualFactoryGetImageVisual4: Request image visual with a Property::Map, test custom wrap mode and pixel area without atlasing" ); - - VisualFactory factory = VisualFactory::Get(); - DALI_TEST_CHECK( factory ); - - // Test wrap mode without atlasing. Image with a size bigger than 512*512 will NOT be uploaded as a part of the atlas. - const int width=600; - const int height=600; - const Vector4 pixelArea(-0.5f, -0.5f, 2.f, 2.f); - - Property::Map propertyMap; - propertyMap.Insert( Visual::Property::TYPE, Visual::IMAGE ); - propertyMap.Insert( ImageVisual::Property::URL, gImage_600_RGB ); - propertyMap.Insert( ImageVisual::Property::DESIRED_WIDTH, width ); - propertyMap.Insert( ImageVisual::Property::DESIRED_HEIGHT, height ); - propertyMap.Insert( ImageVisual::Property::SYNCHRONOUS_LOADING, true ); - propertyMap.Insert( ImageVisual::Property::PIXEL_AREA, pixelArea ); - propertyMap.Insert( ImageVisual::Property::WRAP_MODE_U, WrapMode::MIRRORED_REPEAT ); - propertyMap.Insert( ImageVisual::Property::WRAP_MODE_V, WrapMode::REPEAT ); - - Visual::Base visual = factory.CreateVisual( propertyMap ); - DALI_TEST_CHECK( visual ); - - TestGlAbstraction& gl = application.GetGlAbstraction(); - TraceCallStack& textureTrace = gl.GetTextureTrace(); - textureTrace.Enable(true); - TraceCallStack& texParameterTrace = gl.GetTexParameterTrace(); - texParameterTrace.Enable( true ); - - DummyControl actor = DummyControl::New(); - DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); - dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual ); - actor.SetSize(2000, 2000); - actor.SetParentOrigin(ParentOrigin::CENTER); - Stage::GetCurrent().Add( actor ); - - // loading started - application.SendNotification(); - application.Render(); - application.Render(); - application.SendNotification(); - BitmapLoader loader = BitmapLoader::GetLatestCreated(); - DALI_TEST_CHECK( loader ); - loader.WaitForLoading();// waiting until the image to be loaded - DALI_TEST_CHECK( loader.IsLoaded() ); - - DALI_TEST_CHECK( actor.GetRendererCount() == 1u ); - - DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION ); - - // WITHOUT atlasing, the wrapping is handled by setting gl texture parameters - std::stringstream out; - out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_S << ", " << GL_MIRRORED_REPEAT; - DALI_TEST_CHECK( texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); - out.str(""); - out << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_T << ", " << GL_REPEAT; - DALI_TEST_CHECK( texParameterTrace.FindMethodAndParams("TexParameteri", out.str()) ); - - // test the uniforms which used to handle the wrap mode - Renderer renderer = actor.GetRendererAt( 0u ); - DALI_TEST_CHECK( renderer ); - - Property::Value pixelAreaValue = renderer.GetProperty( renderer.GetPropertyIndex( "pixelArea" ) ); - DALI_TEST_EQUALS( pixelAreaValue.Get(), pixelArea, TEST_LOCATION ); - Vector4 pixelAreaUniform; - DALI_TEST_CHECK( gl.GetUniformValue( "pixelArea", pixelAreaUniform ) ); - DALI_TEST_EQUALS( pixelArea, pixelAreaUniform, Math::MACHINE_EPSILON_100, TEST_LOCATION ); - - Property::Index wrapModeIndex = renderer.GetPropertyIndex( "wrapMode" ); - DALI_TEST_CHECK(wrapModeIndex == Property::INVALID_INDEX); - - actor.Unparent(); - DALI_TEST_CHECK( actor.GetRendererCount() == 0u ); - - END_TEST; -} int UtcDaliVisualFactoryGetNPatchVisual1(void) { diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 0799c9e..c0dd44b 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -28,6 +28,8 @@ toolkit_src_files = \ $(toolkit_src_dir)/visuals/svg/svg-visual.cpp \ $(toolkit_src_dir)/visuals/text/text-visual.cpp \ $(toolkit_src_dir)/visuals/transition-data-impl.cpp \ + $(toolkit_src_dir)/visuals/texture-manager.cpp \ + $(toolkit_src_dir)/visuals/texture-upload-observer.cpp \ $(toolkit_src_dir)/visuals/visual-base-data-impl.cpp \ $(toolkit_src_dir)/visuals/visual-base-impl.cpp \ $(toolkit_src_dir)/visuals/visual-factory-cache.cpp \ diff --git a/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp b/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp index 1fbef38..8454992 100644 --- a/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp +++ b/dali-toolkit/internal/image-loader/async-image-loader-impl.cpp @@ -49,7 +49,7 @@ IntrusivePtr AsyncImageLoader::New() return internal; } -uint32_t AsyncImageLoader::Load( const std::string& url, +uint32_t AsyncImageLoader::Load( const VisualUrl& url, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, @@ -83,7 +83,7 @@ void AsyncImageLoader::CancelAll() void AsyncImageLoader::ProcessLoadedImage() { - while( LoadingTask *next = mLoadThread.NextCompletedTask() ) + while( LoadingTask *next = mLoadThread.NextCompletedTask() ) { mLoadedSignal.Emit( next->id, next->pixelData ); delete next; diff --git a/dali-toolkit/internal/image-loader/async-image-loader-impl.h b/dali-toolkit/internal/image-loader/async-image-loader-impl.h index 92a1652..fe4c73b 100644 --- a/dali-toolkit/internal/image-loader/async-image-loader-impl.h +++ b/dali-toolkit/internal/image-loader/async-image-loader-impl.h @@ -51,7 +51,7 @@ public: /** * @copydoc Toolkit::AsyncImageLoader::Load( const std::string&, ImageDimensions, FittingMode::Type, SamplingMode::Type, bool ) */ - uint32_t Load( const std::string& url, + uint32_t Load( const VisualUrl& url, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, @@ -85,14 +85,11 @@ protected: ~AsyncImageLoader(); private: - Toolkit::AsyncImageLoader::ImageLoadedSignalType mLoadedSignal; ImageLoadThread mLoadThread; uint32_t mLoadTaskId; bool mIsLoadThreadStarted; - - }; } // namespace Internal diff --git a/dali-toolkit/internal/image-loader/image-atlas-impl.cpp b/dali-toolkit/internal/image-loader/image-atlas-impl.cpp index d784d3e..30f1ba7 100644 --- a/dali-toolkit/internal/image-loader/image-atlas-impl.cpp +++ b/dali-toolkit/internal/image-loader/image-atlas-impl.cpp @@ -21,7 +21,7 @@ // EXTERNAL INCLUDES #include #include -#include +#include #include #include @@ -127,7 +127,7 @@ float ImageAtlas::GetOccupancyRate() const void ImageAtlas::SetBrokenImage( const std::string& brokenImageUrl ) { - mBrokenImageSize = ResourceImage::GetImageSize( brokenImageUrl ); + mBrokenImageSize = Dali::GetClosestImageSize( brokenImageUrl ); if(mBrokenImageSize.GetWidth() > 0 && mBrokenImageSize.GetHeight() > 0 ) // check the url is valid { mBrokenImageUrl = brokenImageUrl; @@ -145,7 +145,7 @@ bool ImageAtlas::Upload( Vector4& textureRect, ImageDimensions zero; if( size == zero ) // image size not provided { - dimensions = ResourceImage::GetImageSize( url ); + dimensions = Dali::GetClosestImageSize( url ); if( dimensions == zero ) // Fail to read the image & broken image file exists { if( !mBrokenImageUrl.empty() ) diff --git a/dali-toolkit/internal/image-loader/image-load-thread.cpp b/dali-toolkit/internal/image-loader/image-load-thread.cpp index d0b504d..a06953e 100644 --- a/dali-toolkit/internal/image-loader/image-load-thread.cpp +++ b/dali-toolkit/internal/image-loader/image-load-thread.cpp @@ -30,7 +30,7 @@ namespace Toolkit namespace Internal { -LoadingTask::LoadingTask( uint32_t id, const std::string& url, ImageDimensions dimensions, +LoadingTask::LoadingTask( uint32_t id, const VisualUrl& url, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, bool orientationCorrection ) : pixelData(), url( url ), @@ -44,7 +44,14 @@ LoadingTask::LoadingTask( uint32_t id, const std::string& url, ImageDimensions d void LoadingTask::Load() { - pixelData = Dali::LoadImageFromFile( url, dimensions, fittingMode, samplingMode, orientationCorrection ); + if( url.IsLocal() ) + { + pixelData = Dali::LoadImageFromFile( url.GetUrl(), dimensions, fittingMode, samplingMode, orientationCorrection ); + } + else + { + pixelData = Dali::DownloadImageSynchronously ( url.GetUrl(), dimensions, fittingMode, samplingMode, orientationCorrection ); + } } diff --git a/dali-toolkit/internal/image-loader/image-load-thread.h b/dali-toolkit/internal/image-loader/image-load-thread.h index f46d435..f3d9632 100644 --- a/dali-toolkit/internal/image-loader/image-load-thread.h +++ b/dali-toolkit/internal/image-loader/image-load-thread.h @@ -26,7 +26,7 @@ #include #include #include - +#include namespace Dali { @@ -51,8 +51,9 @@ struct LoadingTask * @param [in] samplingMode The filtering method used when sampling pixels from the input image while fitting it to desired size. * @param [in] orientationCorrection Reorient the image to respect any orientation metadata in its header. */ - LoadingTask( uint32_t id, const std::string& url, ImageDimensions dimensions, - FittingMode::Type fittingMode, SamplingMode::Type samplingMode, bool orientationCorrection ); + LoadingTask( uint32_t id, const VisualUrl& url, ImageDimensions dimensions, + FittingMode::Type fittingMode, SamplingMode::Type samplingMode, + bool orientationCorrection ); /** * Load the image @@ -69,13 +70,13 @@ private: public: - PixelData pixelData; ///< pixelData handle after successfull load - std::string url; ///< url of the image to load - uint32_t id; ///< The unique id associated with this task. - ImageDimensions dimensions; ///< dimensions to load - FittingMode::Type fittingMode; ///< fitting options - SamplingMode::Type samplingMode; ///< sampling options - bool orientationCorrection:1; ///< if orientation correction is needed + PixelData pixelData; ///< pixelData handle after successfull load + VisualUrl url; ///< url of the image to load + uint32_t id; ///< The unique id associated with this task. + ImageDimensions dimensions; ///< dimensions to load + FittingMode::Type fittingMode; ///< fitting options + SamplingMode::Type samplingMode; ///< sampling options + bool orientationCorrection:1; ///< if orientation correction is needed }; diff --git a/dali-toolkit/internal/visuals/image-atlas-manager.cpp b/dali-toolkit/internal/visuals/image-atlas-manager.cpp index fe01571..d959c1a 100644 --- a/dali-toolkit/internal/visuals/image-atlas-manager.cpp +++ b/dali-toolkit/internal/visuals/image-atlas-manager.cpp @@ -20,7 +20,7 @@ // EXTERNAL HEADER #include -#include +#include namespace Dali { @@ -58,7 +58,7 @@ TextureSet ImageAtlasManager::Add( Vector4& textureRect, ImageDimensions zero; if( size == zero ) { - dimensions = ResourceImage::GetImageSize( url ); + dimensions = Dali::GetClosestImageSize( url ); } // big image, atlasing is not applied diff --git a/dali-toolkit/internal/visuals/image/image-visual.cpp b/dali-toolkit/internal/visuals/image/image-visual.cpp index 3c82d75..06a9601d 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.cpp +++ b/dali-toolkit/internal/visuals/image/image-visual.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -261,11 +262,13 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache, mPlacementActor(), mImageUrl( imageUrl ), mDesiredSize( size ), + mTextureId( TextureManager::INVALID_TEXTURE_ID ), mFittingMode( fittingMode ), mSamplingMode( samplingMode ), mWrapModeU( WrapMode::DEFAULT ), mWrapModeV( WrapMode::DEFAULT ), - mAttemptAtlasing( false ) + mAttemptAtlasing( false ), + mTextureLoading( false ) { } @@ -277,11 +280,13 @@ ImageVisual::ImageVisual( VisualFactoryCache& factoryCache, const Image& image ) mPlacementActor(), mImageUrl(), mDesiredSize(), + mTextureId( TextureManager::INVALID_TEXTURE_ID ), mFittingMode( FittingMode::DEFAULT ), mSamplingMode( SamplingMode::DEFAULT ), mWrapModeU( WrapMode::DEFAULT ), mWrapModeV( WrapMode::DEFAULT ), - mAttemptAtlasing( false ) + mAttemptAtlasing( false ), + mTextureLoading( false ) { } @@ -465,16 +470,16 @@ void ImageVisual::GetNaturalSize( Vector2& naturalSize ) } else if( mImageUrl.IsValid() && mImageUrl.GetLocation() == VisualUrl::LOCAL ) { - ImageDimensions dimentions = Dali::GetClosestImageSize( mImageUrl.GetUrl() ); - naturalSize.x = dimentions.GetWidth(); - naturalSize.y = dimentions.GetHeight(); + ImageDimensions dimensions = Dali::GetClosestImageSize( mImageUrl.GetUrl() ); + naturalSize.x = dimensions.GetWidth(); + naturalSize.y = dimensions.GetHeight(); return; } naturalSize = Vector2::ZERO; } -void ImageVisual::CreateRenderer( TextureSet& textures ) +void ImageVisual::CreateRenderer( TextureSet& textureSet ) { Geometry geometry; Shader shader; @@ -509,8 +514,11 @@ void ImageVisual::CreateRenderer( TextureSet& textures ) shader.RegisterProperty( PIXEL_ALIGNED_UNIFORM_NAME, PIXEL_ALIGN_ON ); // Set default to align mImpl->mRenderer = Renderer::New( geometry, shader ); - DALI_ASSERT_DEBUG( textures ); - mImpl->mRenderer.SetTextures( textures ); + if( textureSet ) + { + mImpl->mRenderer.SetTextures( textureSet ); + } + // else still waiting for texture load to finish. //Register transform properties mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT ); @@ -583,12 +591,16 @@ void ImageVisual::LoadResourceSynchronously() BitmapLoader loader = BitmapLoader::New( mImageUrl.GetUrl(), mDesiredSize, mFittingMode, mSamplingMode ); loader.Load(); mPixels = loader.GetPixelData(); + mTextureLoading = false; } } TextureSet ImageVisual::CreateTextureSet( Vector4& textureRect, bool synchronousLoading, bool attemptAtlasing ) { TextureSet textureSet; + + mTextureLoading = false; + textureRect = FULL_TEXTURE_RECT; if( synchronousLoading ) { @@ -622,23 +634,34 @@ TextureSet ImageVisual::CreateTextureSet( Vector4& textureRect, bool synchronous { textureSet = mFactoryCache.GetAtlasManager()->Add( textureRect, mImageUrl.GetUrl(), mDesiredSize, mFittingMode, true, this ); mImpl->mFlags |= Impl::IS_ATLASING_APPLIED; + mTextureLoading = true; } if( !textureSet ) // big image, no atlasing or atlasing failed { mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED; - ResourceImage resourceImage = Dali::ResourceImage::New( mImageUrl.GetUrl(), mDesiredSize, mFittingMode, mSamplingMode ); - resourceImage.LoadingFinishedSignal().Connect( this, &ImageVisual::OnImageLoaded ); - textureSet = TextureSet::New(); - TextureSetImage( textureSet, 0u, resourceImage ); + TextureManager& textureManager = mFactoryCache.GetTextureManager(); + mTextureId = textureManager.RequestLoad( mImageUrl, mDesiredSize, mFittingMode, + mSamplingMode, TextureManager::NO_ATLAS, this ); + + TextureManager::LoadState loadState = textureManager.GetTextureState( mTextureId ); + + mTextureLoading = ( loadState == TextureManager::LOADING ); + + if( loadState == TextureManager::UPLOADED ) + { + // UploadComplete has already been called - keep the same texture set + textureSet = textureManager.GetTextureSet(mTextureId); + } } } - if( !(mImpl->mFlags & Impl::IS_ATLASING_APPLIED) ) + if( ! (mImpl->mFlags & Impl::IS_ATLASING_APPLIED) && textureSet ) { Sampler sampler = Sampler::New(); sampler.SetWrapMode( mWrapModeU, mWrapModeV ); textureSet.SetSampler( 0u, sampler ); } + return textureSet; } @@ -646,7 +669,7 @@ void ImageVisual::InitializeRenderer() { mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED; - if( !mImpl->mCustomShader && mImageUrl.GetLocation() == VisualUrl::LOCAL ) + if( ! mImpl->mCustomShader && mImageUrl.GetLocation() == VisualUrl::LOCAL ) { bool defaultWrapMode = mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE; @@ -697,18 +720,6 @@ void ImageVisual::InitializeRenderer( const Image& image ) ApplyImageToSampler( image ); } -void ImageVisual::UploadCompleted() -{ - // Resource image is loaded. If weak handle is holding a placement actor, it is the time to add the renderer to actor. - Actor actor = mPlacementActor.GetHandle(); - if( actor ) - { - actor.AddRenderer( mImpl->mRenderer ); - // reset the weak handle so that the renderer only get added to actor once - mPlacementActor.Reset(); - } -} - void ImageVisual::DoSetOnStage( Actor& actor ) { if( mImageUrl.IsValid() ) @@ -726,7 +737,6 @@ void ImageVisual::DoSetOnStage( Actor& actor ) } mPlacementActor = actor; - // Search the Actor tree to find if Layer UI behaviour set. Layer layer = actor.GetLayer(); if ( layer && layer.GetBehavior() == Layer::LAYER_3D ) @@ -740,7 +750,7 @@ void ImageVisual::DoSetOnStage( Actor& actor ) mImpl->mRenderer.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, mPixelArea ); } - if( IsSynchronousResourceLoading() || !(mImpl->mFlags & Impl::IS_ATLASING_APPLIED) ) + if( mTextureLoading == false ) { actor.AddRenderer( mImpl->mRenderer ); mPlacementActor.Reset(); @@ -755,10 +765,10 @@ void ImageVisual::DoSetOffStage( Actor& actor ) actor.RemoveRenderer( mImpl->mRenderer); if( mImageUrl.IsValid() ) { - RemoveFromAtlas( mImageUrl.GetUrl() ); + RemoveTexture( mImageUrl.GetUrl() ); mImage.Reset(); } - + mTextureLoading = false; mImpl->mRenderer.Reset(); mPlacementActor.Reset(); } @@ -876,34 +886,74 @@ void ImageVisual::ApplyImageToSampler( const Image& image ) } } -void ImageVisual::OnImageLoaded( ResourceImage image ) +// From existing atlas manager +void ImageVisual::UploadCompleted() { - if( image.GetLoadingState() == Dali::ResourceLoadingFailed ) + // Texture has been uploaded. If weak handle is holding a placement actor, it is the time to add the renderer to actor. + Actor actor = mPlacementActor.GetHandle(); + if( actor ) + { + actor.AddRenderer( mImpl->mRenderer ); + + // reset the weak handle so that the renderer only get added to actor once + mPlacementActor.Reset(); + } + mTextureLoading = false; +} + +// From Texture Manager +void ImageVisual::UploadComplete( bool loadingSuccess, TextureSet textureSet, bool usingAtlas, const Vector4& atlasRectangle ) +{ + Actor actor = mPlacementActor.GetHandle(); + if( actor ) { - Image brokenImage = VisualFactoryCache::GetBrokenVisualImage(); if( mImpl->mRenderer ) { - ApplyImageToSampler( brokenImage ); + actor.AddRenderer( mImpl->mRenderer ); + // reset the weak handle so that the renderer only get added to actor once + mPlacementActor.Reset(); + + if( loadingSuccess ) + { + Sampler sampler = Sampler::New(); + sampler.SetWrapMode( mWrapModeU, mWrapModeV ); + textureSet.SetSampler( 0u, sampler ); + mImpl->mRenderer.SetTextures(textureSet); + } + else + { + Image brokenImage = VisualFactoryCache::GetBrokenVisualImage(); + ApplyImageToSampler( brokenImage ); + } } } + mTextureLoading = false; } -void ImageVisual::RemoveFromAtlas(const std::string& url) +void ImageVisual::RemoveTexture(const std::string& url) { - Vector4 atlasRect( 0.f, 0.f, 1.f, 1.f ); - Property::Index index = mImpl->mRenderer.GetPropertyIndex( ATLAS_RECT_UNIFORM_NAME ); - if( index != Property::INVALID_INDEX ) + if( mTextureId != TextureManager::INVALID_TEXTURE_ID ) { - Property::Value atlasRectValue = mImpl->mRenderer.GetProperty( index ); - atlasRectValue.Get( atlasRect ); + mFactoryCache.GetTextureManager().Remove( mTextureId ); + mTextureId = TextureManager::INVALID_TEXTURE_ID; } + else + { + Vector4 atlasRect( 0.f, 0.f, 1.f, 1.f ); + Property::Index index = mImpl->mRenderer.GetPropertyIndex( ATLAS_RECT_UNIFORM_NAME ); + if( index != Property::INVALID_INDEX ) + { + Property::Value atlasRectValue = mImpl->mRenderer.GetProperty( index ); + atlasRectValue.Get( atlasRect ); + } - TextureSet textureSet = mImpl->mRenderer.GetTextures(); - mImpl->mRenderer.Reset(); + TextureSet textureSet = mImpl->mRenderer.GetTextures(); + mImpl->mRenderer.Reset(); - if( index != Property::INVALID_INDEX ) - { - mFactoryCache.GetAtlasManager()->Remove( textureSet, atlasRect ); + if( index != Property::INVALID_INDEX ) + { + mFactoryCache.GetAtlasManager()->Remove( textureSet, atlasRect ); + } } } diff --git a/dali-toolkit/internal/visuals/image/image-visual.h b/dali-toolkit/internal/visuals/image/image-visual.h index f771f6d..9113437 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.h +++ b/dali-toolkit/internal/visuals/image/image-visual.h @@ -27,6 +27,7 @@ // INTERNAL INCLUDES #include +#include #include #include @@ -94,7 +95,7 @@ typedef IntrusivePtr< ImageVisual > ImageVisualPtr; * If the Visual is in a LayerUI it will pixel align the image, using a Layer3D will disable pixel alignment. * Changing layer behaviour between LayerUI to Layer3D whilst the visual is already staged will not have an effect. */ -class ImageVisual: public Visual::Base, public ConnectionTracker, public AtlasUploadObserver +class ImageVisual: public Visual::Base, public ConnectionTracker, public AtlasUploadObserver, public TextureUploadObserver { public: @@ -231,6 +232,14 @@ public: */ virtual void UploadCompleted(); + /** + * @copydoc TextureUploadObserver::UploadCompleted + * + * To avoid rendering garbage pixels, renderer should be added to actor after the resources are ready. + * This callback is the place to add the renderer as it would be called once the loading is finished. + */ + virtual void UploadComplete( bool success, TextureSet textureSet, bool usingAtlas, const Vector4& atlasRectangle ); + private: /** @@ -286,21 +295,15 @@ private: TextureSet CreateTextureSet( Vector4& textureRect, bool synchronousLoading, bool attemptAtlasing ); /** - * Callback function of image resource loading succeed - * @param[in] image The Image content that we attempted to load from mImageUrl - */ - void OnImageLoaded( ResourceImage image ); - - /** * Set the value to the uTextureRect uniform * @param[in] textureRect The texture rectangular area. */ void SetTextureRectUniform( const Vector4& textureRect ); /** - * Remove the image from atlas if it is not used anymore. + * Remove the texture if it is not used anymore. */ - void RemoveFromAtlas(const std::string& url); + void RemoveTexture(const std::string& url); /** * Helper method to set individual values by index key. @@ -318,13 +321,14 @@ private: VisualUrl mImageUrl; Dali::ImageDimensions mDesiredSize; + TextureManager::TextureId mTextureId; + Dali::FittingMode::Type mFittingMode:3; Dali::SamplingMode::Type mSamplingMode:4; Dali::WrapMode::Type mWrapModeU:3; Dali::WrapMode::Type mWrapModeV:3; - - bool mAttemptAtlasing:1; // If true will attempt atlasing, otherwise create unique texture - + bool mAttemptAtlasing:1; ///< If true will attempt atlasing, otherwise create unique texture + bool mTextureLoading:1; ///< True if the texture is being loaded asynchronously, or false when it has loaded. }; } // namespace Internal diff --git a/dali-toolkit/internal/visuals/texture-manager.cpp b/dali-toolkit/internal/visuals/texture-manager.cpp new file mode 100644 index 0000000..7c8098d --- /dev/null +++ b/dali-toolkit/internal/visuals/texture-manager.cpp @@ -0,0 +1,505 @@ + /* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include "texture-manager.h" + +// EXTERNAL HEADERS +#include +#include +#include + +// INTERNAL HEADERS +#include +#include +#include +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +namespace +{ + +#ifdef DEBUG_ENABLED +Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" ); +#endif + +const uint32_t DEFAULT_ATLAS_SIZE( 1024u ); ///< This size can fit 8 by 8 images of average size 128 * 128 +const Vector4 FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f ); ///< UV Rectangle that covers the full Texture +const char * const BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder +const int INVALID_INDEX( -1 ); ///< Invalid index used to represent a non-existant TextureInfo struct +const int INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index + +} // Anonymous namespace + + +TextureManager::TextureManager() +: mAsyncLocalLoader( Toolkit::AsyncImageLoader::New() ), + mAsyncRemoteLoader( Toolkit::AsyncImageLoader::New() ), + mCurrentTextureId( 0 ) +{ + mAsyncLocalLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncLocalLoadComplete ); + mAsyncRemoteLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncRemoteLoadComplete ); +} + +TextureManager::TextureId TextureManager::RequestLoad( + const VisualUrl& url, + const ImageDimensions desiredSize, + FittingMode::Type fittingMode, + Dali::SamplingMode::Type samplingMode, + const UseAtlas useAtlas, + TextureUploadObserver* observer ) +{ + // First check if the requested Texture is cached. + const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas ); + + // 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; + + // Check if the requested Texture exists in the cache. + if( cacheIndex != INVALID_CACHE_INDEX ) + { + // Mark this texture being used by another client resource. + ++( mTextureInfoContainer[ cacheIndex ].referenceCount ); + textureId = mTextureInfoContainer[ 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 + { + // We need a new Texture. + textureId = GenerateUniqueTextureId(); + mTextureInfoContainer.push_back( TextureInfo( textureId, 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. + TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] ); + + DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n", + textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" : + textureInfo.loadState == TextureManager::LOADING ? "LOADING" : + textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" : + textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" ); + + // Check if we should add the observer. Only do this if we have not loaded yet and it will not have loaded by the end of this method. + switch( textureInfo.loadState ) + { + case TextureManager::NOT_STARTED: + { + LoadTexture( textureInfo ); + ObserveTexture( textureInfo, observer ); + break; + } + case TextureManager::LOADING: + { + ObserveTexture( textureInfo, observer ); + break; + } + case TextureManager::UPLOADED: + { + if( 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, + textureInfo.textureSet, textureInfo.useAtlas, + textureInfo.atlasRect ); + } + break; + } + case TextureManager::CANCELLED: + { + // A cancelled texture hasn't finished loading yet. Treat as a loading texture + // (it's ref count has already been incremented, above) + textureInfo.loadState = TextureManager::LOADING; + ObserveTexture( textureInfo, observer ); + break; + } + } + + // Return the TextureId for which this Texture can now be referenced by externally. + return textureId; +} + +void TextureManager::Remove( const TextureManager::TextureId textureId ) +{ + int textureInfoIndex = GetCacheIndexFromId( textureId ); + if( textureInfoIndex != INVALID_INDEX ) + { + TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] ); + + + DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n", + textureId, textureInfoIndex, + textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" : + textureInfo.loadState == TextureManager::LOADING ? "LOADING" : + textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" : + textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" ); + + // Decrement the reference count and check if this is the last user of this Texture. + if( --textureInfo.referenceCount <= 0 ) + { + // This is the last remove for this Texture. + textureInfo.referenceCount = 0; + bool removeTextureInfo = false; + + // If loaded, we can remove the TextureInfo and the Atlas (if atlased). + if( textureInfo.loadState == UPLOADED ) + { + if( textureInfo.atlas ) + { + textureInfo.atlas.Remove( textureInfo.atlasRect ); + } + removeTextureInfo = true; + } + else if( textureInfo.loadState == LOADING ) + { + // We mark the textureInfo for removal. + // Once the load has completed, this method will be called again. + textureInfo.loadState = CANCELLED; + } + else + { + // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data. + removeTextureInfo = true; + } + + // If the state allows us to remove the TextureInfo data, we do so. + if( removeTextureInfo ) + { + // Permanently remove the textureInfo struct. + mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex ); + } + } + } +} + +TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId ) +{ + LoadState loadState = TextureManager::NOT_STARTED; + + int cacheIndex = GetCacheIndexFromId( textureId ); + if( cacheIndex != INVALID_CACHE_INDEX ) + { + TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] ); + loadState = cachedTextureInfo.loadState; + } + return loadState; +} + +TextureSet TextureManager::GetTextureSet( TextureId textureId ) +{ + TextureSet textureSet;// empty handle + + int cacheIndex = GetCacheIndexFromId( textureId ); + if( cacheIndex != INVALID_CACHE_INDEX ) + { + TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] ); + textureSet = cachedTextureInfo.textureSet; + } + return textureSet; +} + + + +bool TextureManager::LoadTexture( TextureInfo& textureInfo ) +{ + bool success = true; + + if( textureInfo.loadState == NOT_STARTED ) + { + textureInfo.loadState = LOADING; + + if( !textureInfo.loadSynchronously ) + { + 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 ); + } + else + { + mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) ); + mAsyncRemoteLoadingInfoContainer.back().loadId = GetImplementation(mAsyncRemoteLoader).Load( + textureInfo.url, textureInfo.desiredSize, + textureInfo.fittingMode, textureInfo.samplingMode, true ); + } + } + } + + return success; +} + +void TextureManager::ObserveTexture( TextureInfo& textureInfo, + TextureUploadObserver* observer ) +{ + if( observer ) + { + textureInfo.observerList.PushBack( observer ); + observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed ); + } +} + +void TextureManager::AsyncLocalLoadComplete( uint32_t id, PixelData pixelData ) +{ + AsyncLoadComplete( mAsyncLocalLoadingInfoContainer, id, pixelData ); +} + +void TextureManager::AsyncRemoteLoadComplete( uint32_t id, PixelData pixelData ) +{ + AsyncLoadComplete( mAsyncRemoteLoadingInfoContainer, id, pixelData ); +} + +void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, PixelData pixelData ) +{ + DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id ); + + if( loadingContainer.size() >= 1u ) + { + AsyncLoadingInfo loadingInfo = loadingContainer.front(); + + if( loadingInfo.loadId == id ) + { + 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 + { + Remove( textureInfo.textureId ); + } + } + } + + loadingContainer.pop_front(); + } +} + + +bool 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 ) + { + DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::PostLoad() 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 ); + } + } + + if( ! success ) + { + DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() ); + // @todo If the load was unsuccessful, upload the broken image. + } + + // Update the load state. + // Note: This is regardless of success as we care about whether a + // 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: + const unsigned int observerCount = textureInfo.observerList.Count(); + for( unsigned int i = 0; i < observerCount; ++i ) + { + TextureUploadObserver* observer = textureInfo.observerList[i]; + if( observer ) + { + observer->UploadComplete( success, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect ); + observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed ); + } + } + + textureInfo.observerList.Clear(); + + return success; +} + +TextureManager::TextureId TextureManager::GenerateUniqueTextureId() +{ + return mCurrentTextureId++; +} + +int TextureManager::GetCacheIndexFromId( const TextureId textureId ) +{ + const unsigned int size = mTextureInfoContainer.size(); + + for( unsigned int i = 0; i < size; ++i ) + { + if( mTextureInfoContainer[i].textureId == textureId ) + { + return i; + } + } + + DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId ); + return INVALID_CACHE_INDEX; +} + +TextureManager::TextureHash TextureManager::GenerateHash( + const std::string& url, + const ImageDimensions size, + const FittingMode::Type fittingMode, + const Dali::SamplingMode::Type samplingMode, + const UseAtlas useAtlas ) +{ + std::string hashTarget( url ); + const size_t urlLength = hashTarget.length(); + const uint16_t width = size.GetWidth(); + const uint16_t height = size.GetWidth(); + + // If either the width or height has been specified, include the resizing options in the hash + if( width != 0 || height != 0 ) + { + // We are appending 5 bytes to the URL to form the hash input. + hashTarget.resize( urlLength + 5u ); + char* hashTargetPtr = &( hashTarget[ urlLength ] ); + + // Pack the width and height (4 bytes total). + *hashTargetPtr++ = size.GetWidth() & 0xff; + *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff; + *hashTargetPtr++ = size.GetHeight() & 0xff; + *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff; + + // Bit-pack the FittingMode, SamplingMode and atlasing. + // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit + *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas; + } + else + { + // We are not including sizing information, but we still need an extra byte for atlasing. + hashTarget.resize( urlLength + 1u ); + // Add the atlasing to the hash input. + hashTarget[ urlLength ] = useAtlas; + } + + return Dali::CalculateHash( hashTarget ); +} + +int TextureManager::FindCachedTexture( + const TextureManager::TextureHash hash, + const std::string& url, + const ImageDimensions size, + const FittingMode::Type fittingMode, + const Dali::SamplingMode::Type samplingMode, + const bool useAtlas ) +{ + // Default to an invalid ID, in case we do not find a match. + int cacheIndex = INVALID_CACHE_INDEX; + + // Iterate through our hashes to find a match. + const unsigned int count = mTextureInfoContainer.size(); + for( unsigned int i = 0u; i < count; ++i ) + { + if( mTextureInfoContainer[i].hash == hash ) + { + // We have a match, now we check all the original parameters in case of a hash collision. + TextureInfo& textureInfo( mTextureInfoContainer[i] ); + + if( ( url == textureInfo.url.GetUrl() ) && + ( useAtlas == textureInfo.useAtlas ) && + ( size == textureInfo.desiredSize ) && + ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) || + ( fittingMode == textureInfo.fittingMode && + samplingMode == textureInfo.samplingMode ) ) ) + { + // The found Texture is a match. + cacheIndex = i; + break; + } + } + } + + return cacheIndex; +} + +void TextureManager::ObserverDestroyed( TextureUploadObserver* observer ) +{ + const unsigned int count = mTextureInfoContainer.size(); + for( unsigned int i = 0; i < count; ++i ) + { + TextureInfo& textureInfo( mTextureInfoContainer[i] ); + for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); ++j ) + { + if( *j == observer ) + { + textureInfo.observerList.Erase( j ); + break; + } + } + } +} + +TextureManager::~TextureManager() +{ + mTextureInfoContainer.clear(); + mAsyncLocalLoadingInfoContainer.clear(); + mAsyncRemoteLoadingInfoContainer.clear(); +} + + + + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/visuals/texture-manager.h b/dali-toolkit/internal/visuals/texture-manager.h new file mode 100644 index 0000000..312b12e --- /dev/null +++ b/dali-toolkit/internal/visuals/texture-manager.h @@ -0,0 +1,368 @@ +#ifndef DALI_TOOLKIT_TEXTURE_MANAGER_H +#define DALI_TOOLKIT_TEXTURE_MANAGER_H + +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include +#include + + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +/** + * The TextureManager provides a common Image loading API for Visuals. + * + * The TextureManager is responsible for providing sync, async, atlased and non-atlased loads. + * Texture caching is provided and performed when possible. + * Broken Images are automatically provided on load failure. + */ +class TextureManager : public ConnectionTracker +{ +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 + + enum UseAtlas + { + NO_ATLAS, + USE_ATLAS + }; + + enum LoadType + { + LOAD_ASYNCHRONOUSLY, + LOAD_SYNCHRONOUSLY + }; + + /** + * @brief The LoadState Enumeration represents the current state of a particular Textures life-cycle. + */ + enum LoadState + { + NOT_STARTED, ///< Default + LOADING, ///< Loading has been started, but not finished. + UPLOADED, ///< Loaded (and ready). + CANCELLED, ///< Removed before loading completed + }; + +public: + + /** + * Constructor. + */ + TextureManager(); + + /** + * Destructor. + */ + ~TextureManager(); + + +// TextureManager Main API: + + /** + * @brief Requests an image load of the given URL. + * + * 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, + const ImageDimensions desiredSize, + FittingMode::Type fittingMode, + Dali::SamplingMode::Type samplingMode, + const UseAtlas useAtlasing, + TextureUploadObserver* observer ); + + /** + * @brief Remove a Texture from the TextureManager. + * + * Textures are cached and therefore only the removal of the last + * occurrence of a Texture will cause its removal internally. + * + * @param[in] textureId The ID of the Texture to remove. + */ + void Remove( const TextureManager::TextureId textureId ); + + /** + * @brief Get the current state of a texture + * @param[in] textureId The texture id to query + * @return The loading state if the texture is valid, or NOT_STARTED if the textureId + * is not valid. + */ + LoadState GetTextureState( TextureId textureId ); + + /** + * @brief Get the associated texture set if the texture id is valid + * @param[in] textureId The texture Id to look up + * @return the associated texture set, or an empty handle if textureId is not valid + */ + TextureSet GetTextureSet( TextureId textureId ); + +private: + + + 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, + const VisualUrl& url, + ImageDimensions desiredSize, + FittingMode::Type fittingMode, + Dali::SamplingMode::Type samplingMode, + bool loadSynchronously, + UseAtlas useAtlas, + TextureManager::TextureHash hash ) + : url( url ), + desiredSize( desiredSize ), + useSize( desiredSize ), + atlasRect( 0.0f, 0.0f, 1.0f, 1.0f ), // Full atlas rectangle + textureId( textureId ), + hash( hash ), + referenceCount( 1u ), + loadState( NOT_STARTED ), + fittingMode( fittingMode ), + samplingMode( samplingMode ), + loadSynchronously( loadSynchronously ), + useAtlas( useAtlas ), + loadingSucceeded( false ) + { + } + + /** + * Container type used to store all observer clients of this Texture + */ + typedef Dali::Vector< TextureUploadObserver* > ObserverListType; + + 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) + 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 + 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 + }; + + // Structs: + + /** + * Struct to hold information about a requested Async load. + * This is used to look up a TextureManager::TextureId from the returned AsyncLoad Id. + */ + struct AsyncLoadingInfo + { + AsyncLoadingInfo( TextureId textureId ) + : textureId( textureId ), + loadId( 0 ) + { + } + + TextureId textureId; ///< The external Texture Id assigned to this load + unsigned short loadId; ///< The load Id used by the async loader to reference this load + }; + + /** + * @brief This struct is used within a container to manage atlas creation and destruction. + */ + struct AtlasInfo + { + AtlasInfo( Toolkit::ImageAtlas atlas, TextureSet textureSet ) + : atlas( atlas ), + textureSet( textureSet ) + { + } + + Toolkit::ImageAtlas atlas; ///< The ImageAtlas object + TextureSet textureSet; ///< The TextureSet is kept in the struct to allow fast lookup of TextureSet to Atlas + }; + + // Private typedefs: + + typedef std::deque AsyncLoadingInfoContainerType; ///< The container type used to manage Asynchronous loads in progress + typedef std::vector AtlasInfoContainerType; ///< The container type used to manage Atlas creation and destruction + typedef std::vector TextureInfoContainerType; ///< The container type used to manage the life-cycle and caching of Textures + + /** + * @brief Used internally to initiate a load. + * @param[in] textureInfo The TextureInfo struct associated with the Texture + * @return True if the load was initiated + */ + bool LoadTexture( TextureInfo& textureInfo ); + + /** + * Add the observer to the observer list + * @param[in] textureInfo The TextureInfo struct associated with the texture + * observer The observer wishing to observe the texture upload + */ + void ObserveTexture( TextureInfo & textureInfo, TextureUploadObserver* observer ); + + /** + * @brief This signal handler is called when the async local loader finishes loading. + * @param[in] id This is the async image loaders Id + * @param[in] pixelData The loaded image data + */ + void AsyncLocalLoadComplete( uint32_t id, PixelData pixelData ); + + /** + * @brief This signal handler is called when the async local loader finishes loading. + * @param[in] id This is the async image loaders Id + * @param[in] pixelData The loaded image data + */ + void AsyncRemoteLoadComplete( uint32_t id, PixelData pixelData ); + + /** + * Common method to handle loading completion + * @param[in] container The Async loading container + * @param[in] id This is the async image loaders Id + * @param[in] pixelData The loaded image data + */ + void AsyncLoadComplete( AsyncLoadingInfoContainerType& container, uint32_t id, PixelData pixelData ); + + /** + * @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 + */ + bool PostLoad( TextureManager::TextureInfo& textureInfo, PixelData pixelData ); + + /** + * @brief Generates a new, unique TextureId + * @return A unique TextureId + */ + TextureManager::TextureId GenerateUniqueTextureId(); + + /** + * @brief Used to lookup an index into the TextureInfoContainer from a TextureId + * @param[in] textureId The TextureId to look up + * @return The cache index + */ + int GetCacheIndexFromId( TextureId textureId ); + + + /** + * @brief Generates a hash for caching based on the input parameters. + * @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 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 ); + + /** + * @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. + */ + 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 ); + + +private: + + /** + * Undefined copy constructor. + */ + TextureManager( const TextureManager& ); + + /** + * Undefined assignment operator. + */ + TextureManager& operator=( const TextureManager& rhs ); + + /** + * This is called by the TextureManagerUploadObserver when an observer is destroyed. + * We use the callback to know when to remove an observer from our notify list. + * @param[in] observer The observer that generated the callback + */ + void ObserverDestroyed( TextureUploadObserver* observer ); + +private: // Member Variables: + + AsyncLoadingInfoContainerType mAsyncLocalLoadingInfoContainer; ///< Used to manage Asynchronous loads in progress + AsyncLoadingInfoContainerType mAsyncRemoteLoadingInfoContainer; ///< Used to manage Asynchronous loads in progress + AtlasInfoContainerType mAtlasContainer; ///< Used to manage Atlas creation and destruction + TextureInfoContainerType mTextureInfoContainer; ///< Used to manage the life-cycle and caching of Textures + Toolkit::AsyncImageLoader mAsyncLocalLoader; ///< The Asynchronous image loader used to provide all local async loads + Toolkit::AsyncImageLoader mAsyncRemoteLoader; ///< The Asynchronous image loader used to provide all remote async loads + TextureId mCurrentTextureId; ///< The current value used for the unique Texture Id generation + +}; + + +} // name Internal + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_TEXTURE_MANAGER_H diff --git a/dali-toolkit/internal/visuals/texture-upload-observer.cpp b/dali-toolkit/internal/visuals/texture-upload-observer.cpp new file mode 100644 index 0000000..8ffd35f --- /dev/null +++ b/dali-toolkit/internal/visuals/texture-upload-observer.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include "texture-upload-observer.h" + +namespace Dali +{ + +namespace Toolkit +{ + +TextureUploadObserver::TextureUploadObserver() +{ +} + +TextureUploadObserver::~TextureUploadObserver() +{ + if( !mDestructionSignal.Empty() ) + { + mDestructionSignal.Emit( this ); + } +} + +TextureUploadObserver::DestructionSignalType& TextureUploadObserver::DestructionSignal() +{ + return mDestructionSignal; +} + + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/visuals/texture-upload-observer.h b/dali-toolkit/internal/visuals/texture-upload-observer.h new file mode 100644 index 0000000..c300274 --- /dev/null +++ b/dali-toolkit/internal/visuals/texture-upload-observer.h @@ -0,0 +1,84 @@ +#ifndef DALI_TOOLKIT_INTERNAL_TEXTURE_UPLOAD_OBSERVER_H +#define DALI_TOOLKIT_INTERNAL_TEXTURE_UPLOAD_OBSERVER_H + +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +namespace Dali +{ + +class TextureSet; + +namespace Toolkit +{ + + +/** + * @brief Base class used to observe the upload status of a texture. + * + * Derived class must implement the UploadComplete method which is + * executed once the texture is ready to draw. + */ +class DALI_IMPORT_API TextureUploadObserver +{ +public: + + typedef Signal< void ( TextureUploadObserver* ) > DestructionSignalType; ///< Signal prototype for the Destruction Signal. + + /** + * @brief Constructor. + */ + TextureUploadObserver(); + + /** + * @brief Virtual destructor. + */ + virtual ~TextureUploadObserver(); + + /** + * The action to be taken once the async load has finished and the upload to GPU is completed. + * This should be overridden by the deriving class. + * + * @param[in] loadSuccess True if the texture load was successful (i.e. the resource is available). If false, then the resource failed to load. In future, this will automatically upload a "broken" image. + * @param[in] textureSet The TextureSet containing the Texture + * @param[in] useAtlasing True if atlasing was used (note: this may be different to what was requested) + * @param[in] atlasRect If using atlasing, this is the rectangle within the atlas to use. + */ + virtual void UploadComplete( bool loadSuccess, TextureSet textureSet, bool useAtlasing, const Vector4& atlasRect ) = 0; + + /** + * @brief Returns the destruction signal. + * This is emitted when the observer is destroyed. + * This is used by the observer notifier to mark this observer as destroyed (IE. It no longer needs notifying). + */ + DestructionSignalType& DestructionSignal(); + +private: + + DestructionSignalType mDestructionSignal; ///< The destruction signal emitted when the observer is destroyed. + +}; + + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_INTERNAL_TEXTURE_UPLOAD_OBSERVER_H diff --git a/dali-toolkit/internal/visuals/visual-factory-cache.cpp b/dali-toolkit/internal/visuals/visual-factory-cache.cpp index dd8d306..d9a566d 100644 --- a/dali-toolkit/internal/visuals/visual-factory-cache.cpp +++ b/dali-toolkit/internal/visuals/visual-factory-cache.cpp @@ -112,6 +112,11 @@ ImageAtlasManagerPtr VisualFactoryCache::GetAtlasManager() return mAtlasManager; } +TextureManager& VisualFactoryCache::GetTextureManager() +{ + return mTextureManager; +} + NPatchLoader& VisualFactoryCache::GetNPatchLoader() { return mNPatchLoader; diff --git a/dali-toolkit/internal/visuals/visual-factory-cache.h b/dali-toolkit/internal/visuals/visual-factory-cache.h index 470cf07..cc68e0f 100644 --- a/dali-toolkit/internal/visuals/visual-factory-cache.h +++ b/dali-toolkit/internal/visuals/visual-factory-cache.h @@ -28,6 +28,7 @@ // INTERNAL INCLUDES #include #include +#include namespace Dali { @@ -37,11 +38,12 @@ namespace Toolkit namespace Internal { - class ImageAtlasManager; +class NPatchLoader; +class TextureManager; + typedef IntrusivePtr ImageAtlasManagerPtr; -class NPatchLoader; /** * Caches shaders and geometries. Owned by VisualFactory. @@ -145,6 +147,12 @@ public: ImageAtlasManagerPtr GetAtlasManager(); /** + * Get the texture manager + * @return A reference to the texture manager + */ + TextureManager& GetTextureManager(); + + /** * Get the N-Patch texture cache. * @return A reference to the N patch loader */ @@ -185,8 +193,8 @@ private: Shader mShader[SHADER_TYPE_MAX+1]; ImageAtlasManagerPtr mAtlasManager; - NPatchLoader mNPatchLoader; - + TextureManager mTextureManager; + NPatchLoader mNPatchLoader; SvgRasterizeThread* mSvgRasterizeThread; }; diff --git a/dali-toolkit/public-api/image-loader/async-image-loader.cpp b/dali-toolkit/public-api/image-loader/async-image-loader.cpp index c1e23ee..b01134d 100644 --- a/dali-toolkit/public-api/image-loader/async-image-loader.cpp +++ b/dali-toolkit/public-api/image-loader/async-image-loader.cpp @@ -19,6 +19,7 @@ // INTERNAL INCLUDES #include +#include namespace Dali { @@ -63,12 +64,12 @@ AsyncImageLoader AsyncImageLoader::New() uint32_t AsyncImageLoader::Load( const std::string& url ) { - return GetImplementation( *this ).Load( url, ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, true ); + return GetImplementation( *this ).Load( Toolkit::Internal::VisualUrl(url), ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, true ); } uint32_t AsyncImageLoader::Load( const std::string& url, ImageDimensions dimensions ) { - return GetImplementation( *this ).Load( url, dimensions, FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, true ); + return GetImplementation( *this ).Load( Toolkit::Internal::VisualUrl(url), dimensions, FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, true ); } uint32_t AsyncImageLoader::Load( const std::string& url, @@ -77,7 +78,7 @@ uint32_t AsyncImageLoader::Load( const std::string& url, SamplingMode::Type samplingMode, bool orientationCorrection ) { - return GetImplementation(*this).Load( url, dimensions, fittingMode, samplingMode, orientationCorrection ); + return GetImplementation(*this).Load( Toolkit::Internal::VisualUrl(url), dimensions, fittingMode, samplingMode, orientationCorrection ); } bool AsyncImageLoader::Cancel( uint32_t loadingTaskId ) -- 2.7.4