2 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include "texture-manager.h"
22 #include <dali/devel-api/common/hash.h>
23 #include <dali/devel-api/images/texture-set-image.h>
24 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
25 #include <dali/integration-api/debug.h>
28 #include <dali/integration-api/debug.h>
29 #include <dali-toolkit/devel-api/image-loader/async-image-loader-devel.h>
30 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
31 #include <dali-toolkit/internal/image-loader/async-image-loader-impl.h>
32 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
48 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
51 const uint32_t DEFAULT_ATLAS_SIZE( 1024u ); ///< This size can fit 8 by 8 images of average size 128 * 128
52 const Vector4 FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f ); ///< UV Rectangle that covers the full Texture
53 const char * const BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder
54 const int INVALID_INDEX( -1 ); ///< Invalid index used to represent a non-existant TextureInfo struct
55 const int INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index
57 } // Anonymous namespace
60 TextureManager::TextureManager()
61 : mAsyncLocalLoader( Toolkit::AsyncImageLoader::New() ),
62 mAsyncRemoteLoader( Toolkit::AsyncImageLoader::New() ),
63 mCurrentTextureId( 0 )
65 DevelAsyncImageLoader::PixelBufferLoadedSignal(mAsyncLocalLoader).Connect( this, &TextureManager::AsyncLocalLoadComplete );
66 DevelAsyncImageLoader::PixelBufferLoadedSignal(mAsyncRemoteLoader).Connect( this, &TextureManager::AsyncRemoteLoadComplete );
69 TextureManager::TextureId TextureManager::RequestLoad(
71 const ImageDimensions desiredSize,
72 FittingMode::Type fittingMode,
73 Dali::SamplingMode::Type samplingMode,
74 const UseAtlas useAtlas,
75 TextureUploadObserver* observer )
77 return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, UPLOAD_TO_TEXTURE, observer );
80 TextureManager::TextureId TextureManager::RequestLoad(
82 TextureId maskTextureId,
84 const ImageDimensions desiredSize,
85 FittingMode::Type fittingMode,
86 Dali::SamplingMode::Type samplingMode,
87 const UseAtlas useAtlas,
89 TextureUploadObserver* observer )
91 return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, UPLOAD_TO_TEXTURE, observer );
94 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
96 // Use the normal load procedure to get the alpha mask.
97 return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, false, KEEP_PIXEL_BUFFER, NULL );
101 TextureManager::TextureId TextureManager::RequestLoadInternal(
102 const VisualUrl& url,
103 TextureId maskTextureId,
105 const ImageDimensions desiredSize,
106 FittingMode::Type fittingMode,
107 Dali::SamplingMode::Type samplingMode,
110 StorageType storageType,
111 TextureUploadObserver* observer )
113 // First check if the requested Texture is cached.
114 const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
116 TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
118 // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
119 int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
121 // Check if the requested Texture exists in the cache.
122 if( cacheIndex != INVALID_CACHE_INDEX )
124 // Mark this texture being used by another client resource.
125 ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
126 textureId = mTextureInfoContainer[ cacheIndex ].textureId;
128 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 );
131 if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
133 // We need a new Texture.
134 textureId = GenerateUniqueTextureId();
135 mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
136 desiredSize, contentScale, fittingMode, samplingMode,
137 false, cropToMask, useAtlas, textureHash ) );
138 cacheIndex = mTextureInfoContainer.size() - 1u;
140 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 );
143 // The below code path is common whether we are using the cache or not.
144 // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
145 // or a new TextureInfo just created.
146 TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
147 textureInfo.maskTextureId = maskTextureId;
148 textureInfo.storageType = storageType;
150 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
151 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
152 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
153 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
154 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
156 // 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.
157 switch( textureInfo.loadState )
159 case TextureManager::NOT_STARTED:
161 LoadTexture( textureInfo );
162 ObserveTexture( textureInfo, observer );
165 case TextureManager::LOADING:
167 ObserveTexture( textureInfo, observer );
170 case TextureManager::UPLOADED:
174 // The Texture has already loaded. The other observers have already been notified.
175 // We need to send a "late" loaded notification for this observer.
176 observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
177 textureInfo.useAtlas, textureInfo.atlasRect );
181 case TextureManager::CANCELLED:
183 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
184 // (it's ref count has already been incremented, above)
185 textureInfo.loadState = TextureManager::LOADING;
186 ObserveTexture( textureInfo, observer );
189 case TextureManager::LOAD_FINISHED:
190 case TextureManager::WAITING_FOR_MASK:
191 case TextureManager::LOAD_FAILED:
192 // Loading has already completed. Do nothing.
196 // Return the TextureId for which this Texture can now be referenced by externally.
200 void TextureManager::Remove( const TextureManager::TextureId textureId )
202 int textureInfoIndex = GetCacheIndexFromId( textureId );
203 if( textureInfoIndex != INVALID_INDEX )
205 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
207 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
208 textureId, textureInfoIndex,
209 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
210 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
211 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
212 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
214 // Decrement the reference count and check if this is the last user of this Texture.
215 if( --textureInfo.referenceCount <= 0 )
217 // This is the last remove for this Texture.
218 textureInfo.referenceCount = 0;
219 bool removeTextureInfo = false;
221 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
222 if( textureInfo.loadState == UPLOADED )
224 if( textureInfo.atlas )
226 textureInfo.atlas.Remove( textureInfo.atlasRect );
228 removeTextureInfo = true;
230 else if( textureInfo.loadState == LOADING )
232 // We mark the textureInfo for removal.
233 // Once the load has completed, this method will be called again.
234 textureInfo.loadState = CANCELLED;
238 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
239 removeTextureInfo = true;
242 // If the state allows us to remove the TextureInfo data, we do so.
243 if( removeTextureInfo )
245 // Permanently remove the textureInfo struct.
246 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
252 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
254 int cacheIndex = GetCacheIndexFromId( textureId );
255 DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
257 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
258 return cachedTextureInfo.url;
261 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
263 LoadState loadState = TextureManager::NOT_STARTED;
265 int cacheIndex = GetCacheIndexFromId( textureId );
266 if( cacheIndex != INVALID_CACHE_INDEX )
268 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
269 loadState = cachedTextureInfo.loadState;
274 TextureSet TextureManager::GetTextureSet( TextureId textureId )
276 TextureSet textureSet;// empty handle
278 int cacheIndex = GetCacheIndexFromId( textureId );
279 if( cacheIndex != INVALID_CACHE_INDEX )
281 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
282 textureSet = cachedTextureInfo.textureSet;
287 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
291 if( textureInfo.loadState == NOT_STARTED )
293 textureInfo.loadState = LOADING;
295 if( !textureInfo.loadSynchronously )
297 if( textureInfo.url.IsLocal() )
299 mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
300 mAsyncLocalLoadingInfoContainer.back().loadId =
301 GetImplementation(mAsyncLocalLoader).Load( textureInfo.url, textureInfo.desiredSize,
302 textureInfo.fittingMode,
303 textureInfo.samplingMode, true );
307 mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
308 mAsyncRemoteLoadingInfoContainer.back().loadId =
309 GetImplementation(mAsyncRemoteLoader).Load( textureInfo.url, textureInfo.desiredSize,
310 textureInfo.fittingMode,
311 textureInfo.samplingMode, true );
319 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
320 TextureUploadObserver* observer )
324 textureInfo.observerList.PushBack( observer );
325 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
329 void TextureManager::AsyncLocalLoadComplete( uint32_t id, Devel::PixelBuffer pixelBuffer )
331 AsyncLoadComplete( mAsyncLocalLoadingInfoContainer, id, pixelBuffer );
334 void TextureManager::AsyncRemoteLoadComplete( uint32_t id, Devel::PixelBuffer pixelBuffer )
336 AsyncLoadComplete( mAsyncRemoteLoadingInfoContainer, id, pixelBuffer );
339 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
341 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
343 if( loadingContainer.size() >= 1u )
345 AsyncLoadingInfo loadingInfo = loadingContainer.front();
347 if( loadingInfo.loadId == id )
349 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
350 if( cacheIndex != INVALID_CACHE_INDEX )
352 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
354 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
356 if( textureInfo.loadState != CANCELLED )
358 // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
359 PostLoad( textureInfo, pixelBuffer );
363 Remove( textureInfo.textureId );
368 loadingContainer.pop_front();
372 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
374 // Was the load successful?
375 if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
377 // No atlas support for now
378 textureInfo.useAtlas = NO_ATLAS;
380 if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
382 // If there is a mask texture ID associated with this texture, then apply the mask
383 // if it's already loaded. If it hasn't, and the mask is still loading,
384 // wait for the mask to finish loading.
385 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
387 LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
388 if( maskLoadState == LOADING )
390 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
391 textureInfo.loadState = WAITING_FOR_MASK;
393 else if( maskLoadState == LOAD_FINISHED )
395 ApplyMask( pixelBuffer, textureInfo.maskTextureId, textureInfo.scaleFactor, textureInfo.cropToMask );
396 UploadTexture( pixelBuffer, textureInfo );
397 NotifyObservers( textureInfo, true );
402 UploadTexture( pixelBuffer, textureInfo );
403 NotifyObservers( textureInfo, true );
408 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
409 textureInfo.loadState = LOAD_FINISHED;
411 // Check if there was another texture waiting for this load to complete
412 // (e.g. if this was an image mask, and its load is on a different thread)
413 CheckForWaitingTexture( textureInfo );
418 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
419 // @todo If the load was unsuccessful, upload the broken image.
420 textureInfo.loadState = LOAD_FAILED;
421 CheckForWaitingTexture( textureInfo );
422 NotifyObservers( textureInfo, false );
426 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
428 // Search the cache, checking if any texture has this texture id as a
430 const unsigned int size = mTextureInfoContainer.size();
432 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
434 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
435 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
437 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
438 Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
439 textureInfo.pixelBuffer.Reset();
441 if( maskTextureInfo.loadState == LOAD_FINISHED )
443 ApplyMask( pixelBuffer, maskTextureInfo.textureId, textureInfo.scaleFactor, textureInfo.cropToMask );
444 UploadTexture( pixelBuffer, textureInfo );
445 NotifyObservers( textureInfo, true );
449 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
450 textureInfo.loadState = LOAD_FAILED;
451 NotifyObservers( textureInfo, false );
457 void TextureManager::ApplyMask(
458 Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId,
459 float contentScale, bool cropToMask )
461 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
462 Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
463 pixelBuffer.ApplyMask( maskPixelBuffer, contentScale, cropToMask );
466 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
468 if( textureInfo.useAtlas != USE_ATLAS )
470 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
472 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(), pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
473 PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
474 texture.Upload( pixelData );
475 textureInfo.textureSet = TextureSet::New();
476 textureInfo.textureSet.SetTexture( 0u, texture );
479 // Update the load state.
480 // Note: This is regardless of success as we care about whether a
481 // load attempt is in progress or not. If unsuccessful, a broken
482 // image is still loaded.
483 textureInfo.loadState = UPLOADED;
486 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
488 TextureId textureId = textureInfo.textureId;
490 // If there is an observer: Notify the load is complete, whether successful or not,
491 // and erase it from the list
492 unsigned int observerCount = textureInfo.observerList.Count();
493 TextureInfo* info = &textureInfo;
495 while( observerCount )
497 TextureUploadObserver* observer = info->observerList[0];
499 // During UploadComplete() a Control ResourceReady() signal is emitted.
500 // During that signal the app may add remove /add Textures (e.g. via
501 // ImageViews). At this point no more observers can be added to the
502 // observerList, because textureInfo.loadState = UPLOADED. However it is
503 // possible for observers to be removed, hence we check the observer list
504 // count every iteration.
506 // The reference to the textureInfo struct can also become invalidated,
507 // because new load requests can modify the mTextureInfoContainer list
508 // (e.g. if more requests are pushed back it can cause the list to be
509 // resized invalidating the reference to the TextureInfo ).
510 observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
511 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
513 // Get the textureInfo from the container again as it may have been
516 int textureInfoIndex = GetCacheIndexFromId( textureId );
517 if( textureInfoIndex == INVALID_CACHE_INDEX)
519 return; // texture has been removed - can stop.
522 info = &mTextureInfoContainer[ textureInfoIndex ];
523 observerCount = info->observerList.Count();
524 if ( observerCount > 0 )
526 // remove the observer that was just triggered if it's still in the list
527 for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
531 info->observerList.Erase( j );
540 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
542 return mCurrentTextureId++;
545 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
547 const unsigned int size = mTextureInfoContainer.size();
549 for( unsigned int i = 0; i < size; ++i )
551 if( mTextureInfoContainer[i].textureId == textureId )
557 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
558 return INVALID_CACHE_INDEX;
561 TextureManager::TextureHash TextureManager::GenerateHash(
562 const std::string& url,
563 const ImageDimensions size,
564 const FittingMode::Type fittingMode,
565 const Dali::SamplingMode::Type samplingMode,
566 const UseAtlas useAtlas,
567 TextureId maskTextureId )
569 std::string hashTarget( url );
570 const size_t urlLength = hashTarget.length();
571 const uint16_t width = size.GetWidth();
572 const uint16_t height = size.GetWidth();
574 // If either the width or height has been specified, include the resizing options in the hash
575 if( width != 0 || height != 0 )
577 // We are appending 5 bytes to the URL to form the hash input.
578 hashTarget.resize( urlLength + 5u );
579 char* hashTargetPtr = &( hashTarget[ urlLength ] );
581 // Pack the width and height (4 bytes total).
582 *hashTargetPtr++ = size.GetWidth() & 0xff;
583 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
584 *hashTargetPtr++ = size.GetHeight() & 0xff;
585 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
587 // Bit-pack the FittingMode, SamplingMode and atlasing.
588 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
589 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
593 // We are not including sizing information, but we still need an extra byte for atlasing.
594 hashTarget.resize( urlLength + 1u );
595 // Add the atlasing to the hash input.
596 hashTarget[ urlLength ] = useAtlas;
599 if( maskTextureId != INVALID_TEXTURE_ID )
601 hashTarget.resize( urlLength + sizeof( TextureId ) );
602 TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
604 // Append the hash target to the end of the URL byte by byte:
605 // (to avoid SIGBUS / alignment issues)
606 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
608 *hashTargetPtr++ = maskTextureId & 0xff;
609 maskTextureId >>= 8u;
613 return Dali::CalculateHash( hashTarget );
616 int TextureManager::FindCachedTexture(
617 const TextureManager::TextureHash hash,
618 const std::string& url,
619 const ImageDimensions size,
620 const FittingMode::Type fittingMode,
621 const Dali::SamplingMode::Type samplingMode,
623 TextureId maskTextureId)
625 // Default to an invalid ID, in case we do not find a match.
626 int cacheIndex = INVALID_CACHE_INDEX;
628 // Iterate through our hashes to find a match.
629 const unsigned int count = mTextureInfoContainer.size();
630 for( unsigned int i = 0u; i < count; ++i )
632 if( mTextureInfoContainer[i].hash == hash )
634 // We have a match, now we check all the original parameters in case of a hash collision.
635 TextureInfo& textureInfo( mTextureInfoContainer[i] );
637 if( ( url == textureInfo.url.GetUrl() ) &&
638 ( useAtlas == textureInfo.useAtlas ) &&
639 ( maskTextureId == textureInfo.maskTextureId ) &&
640 ( size == textureInfo.desiredSize ) &&
641 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
642 ( fittingMode == textureInfo.fittingMode &&
643 samplingMode == textureInfo.samplingMode ) ) )
645 // The found Texture is a match.
655 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
657 const unsigned int count = mTextureInfoContainer.size();
658 for( unsigned int i = 0; i < count; ++i )
660 TextureInfo& textureInfo( mTextureInfoContainer[i] );
661 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); )
665 j = textureInfo.observerList.Erase( j );
675 TextureManager::~TextureManager()
677 mTextureInfoContainer.clear();
678 mAsyncLocalLoadingInfoContainer.clear();
679 mAsyncRemoteLoadingInfoContainer.clear();
685 } // namespace Internal
687 } // namespace Toolkit