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, desiredSize, fittingMode, samplingMode, useAtlas, UPLOAD_TO_TEXTURE, observer );
80 TextureManager::TextureId TextureManager::RequestLoad(
82 TextureId maskTextureId,
83 const ImageDimensions desiredSize,
84 FittingMode::Type fittingMode,
85 Dali::SamplingMode::Type samplingMode,
86 const UseAtlas useAtlas,
87 TextureUploadObserver* observer )
89 return RequestLoadInternal( url, maskTextureId, desiredSize, fittingMode, samplingMode, useAtlas, UPLOAD_TO_TEXTURE, observer );
92 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
94 // Use the normal load procedure to get the alpha mask.
95 return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, KEEP_PIXEL_BUFFER, NULL );
99 TextureManager::TextureId TextureManager::RequestLoadInternal(
100 const VisualUrl& url,
101 TextureId maskTextureId,
102 const ImageDimensions desiredSize,
103 FittingMode::Type fittingMode,
104 Dali::SamplingMode::Type samplingMode,
106 StorageType storageType,
107 TextureUploadObserver* observer )
109 // First check if the requested Texture is cached.
110 const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
112 TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
114 // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
115 int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
117 // Check if the requested Texture exists in the cache.
118 if( cacheIndex != INVALID_CACHE_INDEX )
120 // Mark this texture being used by another client resource.
121 ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
122 textureId = mTextureInfoContainer[ cacheIndex ].textureId;
124 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 );
127 if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
129 // We need a new Texture.
130 textureId = GenerateUniqueTextureId();
131 mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
132 desiredSize, fittingMode, samplingMode,
133 false, useAtlas, textureHash ) );
134 cacheIndex = mTextureInfoContainer.size() - 1u;
136 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 );
139 // The below code path is common whether we are using the cache or not.
140 // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
141 // or a new TextureInfo just created.
142 TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
143 textureInfo.maskTextureId = maskTextureId;
144 textureInfo.storageType = storageType;
146 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
147 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
148 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
149 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
150 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
152 // 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.
153 switch( textureInfo.loadState )
155 case TextureManager::NOT_STARTED:
157 LoadTexture( textureInfo );
158 ObserveTexture( textureInfo, observer );
161 case TextureManager::LOADING:
163 ObserveTexture( textureInfo, observer );
166 case TextureManager::UPLOADED:
170 // The Texture has already loaded. The other observers have already been notified.
171 // We need to send a "late" loaded notification for this observer.
172 observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
173 textureInfo.useAtlas, textureInfo.atlasRect );
177 case TextureManager::CANCELLED:
179 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
180 // (it's ref count has already been incremented, above)
181 textureInfo.loadState = TextureManager::LOADING;
182 ObserveTexture( textureInfo, observer );
185 case TextureManager::LOAD_FINISHED:
186 case TextureManager::WAITING_FOR_MASK:
187 case TextureManager::LOAD_FAILED:
188 // Loading has already completed. Do nothing.
192 // Return the TextureId for which this Texture can now be referenced by externally.
196 void TextureManager::Remove( const TextureManager::TextureId textureId )
198 int textureInfoIndex = GetCacheIndexFromId( textureId );
199 if( textureInfoIndex != INVALID_INDEX )
201 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
203 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
204 textureId, textureInfoIndex,
205 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
206 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
207 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
208 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
210 // Decrement the reference count and check if this is the last user of this Texture.
211 if( --textureInfo.referenceCount <= 0 )
213 // This is the last remove for this Texture.
214 textureInfo.referenceCount = 0;
215 bool removeTextureInfo = false;
217 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
218 if( textureInfo.loadState == UPLOADED )
220 if( textureInfo.atlas )
222 textureInfo.atlas.Remove( textureInfo.atlasRect );
224 removeTextureInfo = true;
226 else if( textureInfo.loadState == LOADING )
228 // We mark the textureInfo for removal.
229 // Once the load has completed, this method will be called again.
230 textureInfo.loadState = CANCELLED;
234 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
235 removeTextureInfo = true;
238 // If the state allows us to remove the TextureInfo data, we do so.
239 if( removeTextureInfo )
241 // Permanently remove the textureInfo struct.
242 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
248 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
250 int cacheIndex = GetCacheIndexFromId( textureId );
251 DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
253 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
254 return cachedTextureInfo.url;
257 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
259 LoadState loadState = TextureManager::NOT_STARTED;
261 int cacheIndex = GetCacheIndexFromId( textureId );
262 if( cacheIndex != INVALID_CACHE_INDEX )
264 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
265 loadState = cachedTextureInfo.loadState;
270 TextureSet TextureManager::GetTextureSet( TextureId textureId )
272 TextureSet textureSet;// empty handle
274 int cacheIndex = GetCacheIndexFromId( textureId );
275 if( cacheIndex != INVALID_CACHE_INDEX )
277 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
278 textureSet = cachedTextureInfo.textureSet;
283 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
287 if( textureInfo.loadState == NOT_STARTED )
289 textureInfo.loadState = LOADING;
291 if( !textureInfo.loadSynchronously )
293 if( textureInfo.url.IsLocal() )
295 mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
296 mAsyncLocalLoadingInfoContainer.back().loadId =
297 GetImplementation(mAsyncLocalLoader).Load( textureInfo.url, textureInfo.desiredSize,
298 textureInfo.fittingMode,
299 textureInfo.samplingMode, true );
303 mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
304 mAsyncRemoteLoadingInfoContainer.back().loadId =
305 GetImplementation(mAsyncRemoteLoader).Load( textureInfo.url, textureInfo.desiredSize,
306 textureInfo.fittingMode,
307 textureInfo.samplingMode, true );
315 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
316 TextureUploadObserver* observer )
320 textureInfo.observerList.PushBack( observer );
321 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
325 void TextureManager::AsyncLocalLoadComplete( uint32_t id, Devel::PixelBuffer pixelBuffer )
327 AsyncLoadComplete( mAsyncLocalLoadingInfoContainer, id, pixelBuffer );
330 void TextureManager::AsyncRemoteLoadComplete( uint32_t id, Devel::PixelBuffer pixelBuffer )
332 AsyncLoadComplete( mAsyncRemoteLoadingInfoContainer, id, pixelBuffer );
335 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
337 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
339 if( loadingContainer.size() >= 1u )
341 AsyncLoadingInfo loadingInfo = loadingContainer.front();
343 if( loadingInfo.loadId == id )
345 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
346 if( cacheIndex != INVALID_CACHE_INDEX )
348 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
350 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
352 if( textureInfo.loadState != CANCELLED )
354 // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
355 PostLoad( textureInfo, pixelBuffer );
359 Remove( textureInfo.textureId );
364 loadingContainer.pop_front();
368 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
370 // Was the load successful?
371 if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
373 // No atlas support for now
374 textureInfo.useAtlas = NO_ATLAS;
376 if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
378 // If there is a mask texture ID associated with this texture, then apply the mask
379 // if it's already loaded. If it hasn't, and the mask is still loading,
380 // wait for the mask to finish loading.
381 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
383 LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
384 if( maskLoadState == LOADING )
386 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
387 textureInfo.loadState = WAITING_FOR_MASK;
389 else if( maskLoadState == LOAD_FINISHED )
391 ApplyMask( pixelBuffer, textureInfo.maskTextureId );
392 UploadTexture( pixelBuffer, textureInfo );
393 NotifyObservers( textureInfo, true );
398 UploadTexture( pixelBuffer, textureInfo );
399 NotifyObservers( textureInfo, true );
404 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
405 textureInfo.loadState = LOAD_FINISHED;
407 // Check if there was another texture waiting for this load to complete
408 // (e.g. if this was an image mask, and its load is on a different thread)
409 CheckForWaitingTexture( textureInfo );
414 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
415 // @todo If the load was unsuccessful, upload the broken image.
416 textureInfo.loadState = LOAD_FAILED;
417 CheckForWaitingTexture( textureInfo );
418 NotifyObservers( textureInfo, false );
422 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
424 // Search the cache, checking if any texture has this texture id as a
426 const unsigned int size = mTextureInfoContainer.size();
428 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
430 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
431 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
433 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
434 Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
435 textureInfo.pixelBuffer.Reset();
437 if( maskTextureInfo.loadState == LOAD_FINISHED )
439 ApplyMask( pixelBuffer, maskTextureInfo.textureId );
440 UploadTexture( pixelBuffer, textureInfo );
441 NotifyObservers( textureInfo, true );
445 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
446 textureInfo.loadState = LOAD_FAILED;
447 NotifyObservers( textureInfo, false );
453 void TextureManager::ApplyMask( Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId )
455 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
456 Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
457 pixelBuffer.ApplyMask( maskPixelBuffer );
460 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
462 if( textureInfo.useAtlas != USE_ATLAS )
464 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
466 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(), pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
467 PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
468 texture.Upload( pixelData );
469 textureInfo.textureSet = TextureSet::New();
470 textureInfo.textureSet.SetTexture( 0u, texture );
473 // Update the load state.
474 // Note: This is regardless of success as we care about whether a
475 // load attempt is in progress or not. If unsuccessful, a broken
476 // image is still loaded.
477 textureInfo.loadState = UPLOADED;
480 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
482 TextureId textureId = textureInfo.textureId;
484 // If there is an observer: Notify the load is complete, whether successful or not,
485 // and erase it from the list
486 unsigned int observerCount = textureInfo.observerList.Count();
487 TextureInfo* info = &textureInfo;
489 while( observerCount )
491 TextureUploadObserver* observer = info->observerList[0];
493 // During UploadComplete() a Control ResourceReady() signal is emitted.
494 // During that signal the app may add remove /add Textures (e.g. via
495 // ImageViews). At this point no more observers can be added to the
496 // observerList, because textureInfo.loadState = UPLOADED. However it is
497 // possible for observers to be removed, hence we check the observer list
498 // count every iteration.
500 // The reference to the textureInfo struct can also become invalidated,
501 // because new load requests can modify the mTextureInfoContainer list
502 // (e.g. if more requests are pushed back it can cause the list to be
503 // resized invalidating the reference to the TextureInfo ).
504 observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
505 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
507 // Get the textureInfo from the container again as it may have been
510 int textureInfoIndex = GetCacheIndexFromId( textureId );
511 if( textureInfoIndex == INVALID_CACHE_INDEX)
513 return; // texture has been removed - can stop.
516 info = &mTextureInfoContainer[ textureInfoIndex ];
517 observerCount = info->observerList.Count();
518 if ( observerCount > 0 )
520 // remove the observer that was just triggered if it's still in the list
521 for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
525 info->observerList.Erase( j );
534 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
536 return mCurrentTextureId++;
539 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
541 const unsigned int size = mTextureInfoContainer.size();
543 for( unsigned int i = 0; i < size; ++i )
545 if( mTextureInfoContainer[i].textureId == textureId )
551 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
552 return INVALID_CACHE_INDEX;
555 TextureManager::TextureHash TextureManager::GenerateHash(
556 const std::string& url,
557 const ImageDimensions size,
558 const FittingMode::Type fittingMode,
559 const Dali::SamplingMode::Type samplingMode,
560 const UseAtlas useAtlas,
561 TextureId maskTextureId )
563 std::string hashTarget( url );
564 const size_t urlLength = hashTarget.length();
565 const uint16_t width = size.GetWidth();
566 const uint16_t height = size.GetWidth();
568 // If either the width or height has been specified, include the resizing options in the hash
569 if( width != 0 || height != 0 )
571 // We are appending 5 bytes to the URL to form the hash input.
572 hashTarget.resize( urlLength + 5u );
573 char* hashTargetPtr = &( hashTarget[ urlLength ] );
575 // Pack the width and height (4 bytes total).
576 *hashTargetPtr++ = size.GetWidth() & 0xff;
577 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
578 *hashTargetPtr++ = size.GetHeight() & 0xff;
579 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
581 // Bit-pack the FittingMode, SamplingMode and atlasing.
582 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
583 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
587 // We are not including sizing information, but we still need an extra byte for atlasing.
588 hashTarget.resize( urlLength + 1u );
589 // Add the atlasing to the hash input.
590 hashTarget[ urlLength ] = useAtlas;
593 if( maskTextureId != INVALID_TEXTURE_ID )
595 hashTarget.resize( urlLength + sizeof( TextureId ) );
596 TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
598 // Append the hash target to the end of the URL byte by byte:
599 // (to avoid SIGBUS / alignment issues)
600 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
602 *hashTargetPtr++ = maskTextureId & 0xff;
603 maskTextureId >>= 8u;
607 return Dali::CalculateHash( hashTarget );
610 int TextureManager::FindCachedTexture(
611 const TextureManager::TextureHash hash,
612 const std::string& url,
613 const ImageDimensions size,
614 const FittingMode::Type fittingMode,
615 const Dali::SamplingMode::Type samplingMode,
617 TextureId maskTextureId)
619 // Default to an invalid ID, in case we do not find a match.
620 int cacheIndex = INVALID_CACHE_INDEX;
622 // Iterate through our hashes to find a match.
623 const unsigned int count = mTextureInfoContainer.size();
624 for( unsigned int i = 0u; i < count; ++i )
626 if( mTextureInfoContainer[i].hash == hash )
628 // We have a match, now we check all the original parameters in case of a hash collision.
629 TextureInfo& textureInfo( mTextureInfoContainer[i] );
631 if( ( url == textureInfo.url.GetUrl() ) &&
632 ( useAtlas == textureInfo.useAtlas ) &&
633 ( maskTextureId == textureInfo.maskTextureId ) &&
634 ( size == textureInfo.desiredSize ) &&
635 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
636 ( fittingMode == textureInfo.fittingMode &&
637 samplingMode == textureInfo.samplingMode ) ) )
639 // The found Texture is a match.
649 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
651 const unsigned int count = mTextureInfoContainer.size();
652 for( unsigned int i = 0; i < count; ++i )
654 TextureInfo& textureInfo( mTextureInfoContainer[i] );
655 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); )
659 j = textureInfo.observerList.Erase( j );
669 TextureManager::~TextureManager()
671 mTextureInfoContainer.clear();
672 mAsyncLocalLoadingInfoContainer.clear();
673 mAsyncRemoteLoadingInfoContainer.clear();
679 } // namespace Internal
681 } // namespace Toolkit