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,
173 textureInfo.textureSet, textureInfo.useAtlas,
174 textureInfo.atlasRect );
178 case TextureManager::CANCELLED:
180 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
181 // (it's ref count has already been incremented, above)
182 textureInfo.loadState = TextureManager::LOADING;
183 ObserveTexture( textureInfo, observer );
186 case TextureManager::LOAD_FINISHED:
187 case TextureManager::WAITING_FOR_MASK:
188 case TextureManager::LOAD_FAILED:
189 // Loading has already completed. Do nothing.
193 // Return the TextureId for which this Texture can now be referenced by externally.
197 void TextureManager::Remove( const TextureManager::TextureId textureId )
199 int textureInfoIndex = GetCacheIndexFromId( textureId );
200 if( textureInfoIndex != INVALID_INDEX )
202 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
205 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
206 textureId, textureInfoIndex,
207 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
208 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
209 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
210 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
212 // Decrement the reference count and check if this is the last user of this Texture.
213 if( --textureInfo.referenceCount <= 0 )
215 // This is the last remove for this Texture.
216 textureInfo.referenceCount = 0;
217 bool removeTextureInfo = false;
219 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
220 if( textureInfo.loadState == UPLOADED )
222 if( textureInfo.atlas )
224 textureInfo.atlas.Remove( textureInfo.atlasRect );
226 removeTextureInfo = true;
228 else if( textureInfo.loadState == LOADING )
230 // We mark the textureInfo for removal.
231 // Once the load has completed, this method will be called again.
232 textureInfo.loadState = CANCELLED;
236 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
237 removeTextureInfo = true;
240 // If the state allows us to remove the TextureInfo data, we do so.
241 if( removeTextureInfo )
243 // Permanently remove the textureInfo struct.
244 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
250 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
252 int cacheIndex = GetCacheIndexFromId( textureId );
253 DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
255 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
256 return cachedTextureInfo.url;
259 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
261 LoadState loadState = TextureManager::NOT_STARTED;
263 int cacheIndex = GetCacheIndexFromId( textureId );
264 if( cacheIndex != INVALID_CACHE_INDEX )
266 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
267 loadState = cachedTextureInfo.loadState;
272 TextureSet TextureManager::GetTextureSet( TextureId textureId )
274 TextureSet textureSet;// empty handle
276 int cacheIndex = GetCacheIndexFromId( textureId );
277 if( cacheIndex != INVALID_CACHE_INDEX )
279 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
280 textureSet = cachedTextureInfo.textureSet;
285 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
289 if( textureInfo.loadState == NOT_STARTED )
291 textureInfo.loadState = LOADING;
293 if( !textureInfo.loadSynchronously )
295 if( textureInfo.url.IsLocal() )
297 mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
298 mAsyncLocalLoadingInfoContainer.back().loadId =
299 GetImplementation(mAsyncLocalLoader).Load( textureInfo.url, textureInfo.desiredSize,
300 textureInfo.fittingMode,
301 textureInfo.samplingMode, true );
305 mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
306 mAsyncRemoteLoadingInfoContainer.back().loadId =
307 GetImplementation(mAsyncRemoteLoader).Load( textureInfo.url, textureInfo.desiredSize,
308 textureInfo.fittingMode,
309 textureInfo.samplingMode, true );
317 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
318 TextureUploadObserver* observer )
322 textureInfo.observerList.PushBack( observer );
323 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
327 void TextureManager::AsyncLocalLoadComplete( uint32_t id, Devel::PixelBuffer pixelBuffer )
329 AsyncLoadComplete( mAsyncLocalLoadingInfoContainer, id, pixelBuffer );
332 void TextureManager::AsyncRemoteLoadComplete( uint32_t id, Devel::PixelBuffer pixelBuffer )
334 AsyncLoadComplete( mAsyncRemoteLoadingInfoContainer, id, pixelBuffer );
337 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
339 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
341 if( loadingContainer.size() >= 1u )
343 AsyncLoadingInfo loadingInfo = loadingContainer.front();
345 if( loadingInfo.loadId == id )
347 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
348 if( cacheIndex != INVALID_CACHE_INDEX )
350 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
352 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
354 if( textureInfo.loadState != CANCELLED )
356 // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
357 PostLoad( textureInfo, pixelBuffer );
361 Remove( textureInfo.textureId );
366 loadingContainer.pop_front();
370 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
372 // Was the load successful?
373 if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
375 // No atlas support for now
376 textureInfo.useAtlas = NO_ATLAS;
378 if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
380 // If there is a mask texture ID associated with this texture, then apply the mask
381 // if it's already loaded. If it hasn't, and the mask is still loading,
382 // wait for the mask to finish loading.
383 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
385 LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
386 if( maskLoadState == LOADING )
388 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
389 textureInfo.loadState = WAITING_FOR_MASK;
391 else if( maskLoadState == LOAD_FINISHED )
393 ApplyMask( pixelBuffer, textureInfo.maskTextureId );
394 UploadTexture( pixelBuffer, textureInfo );
395 NotifyObservers( textureInfo, true );
400 UploadTexture( pixelBuffer, textureInfo );
401 NotifyObservers( textureInfo, true );
406 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
407 textureInfo.loadState = LOAD_FINISHED;
409 // Check if there was another texture waiting for this load to complete
410 // (e.g. if this was an image mask, and its load is on a different thread)
411 CheckForWaitingTexture( textureInfo );
416 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
417 // @todo If the load was unsuccessful, upload the broken image.
418 textureInfo.loadState = LOAD_FAILED;
419 CheckForWaitingTexture( textureInfo );
420 NotifyObservers( textureInfo, false );
424 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
426 // Search the cache, checking if any texture has this texture id as a
428 const unsigned int size = mTextureInfoContainer.size();
430 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
432 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
433 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
435 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
436 Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
437 textureInfo.pixelBuffer.Reset();
439 if( maskTextureInfo.loadState == LOAD_FINISHED )
441 ApplyMask( pixelBuffer, maskTextureInfo.textureId );
442 UploadTexture( pixelBuffer, textureInfo );
443 NotifyObservers( textureInfo, true );
447 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
448 textureInfo.loadState = LOAD_FAILED;
449 NotifyObservers( textureInfo, false );
455 void TextureManager::ApplyMask( Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId )
457 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
458 Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
459 pixelBuffer.ApplyMask( maskPixelBuffer );
462 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
464 if( textureInfo.useAtlas != USE_ATLAS )
466 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
468 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(), pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
469 PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
470 texture.Upload( pixelData );
471 textureInfo.textureSet = TextureSet::New();
472 textureInfo.textureSet.SetTexture( 0u, texture );
475 // Update the load state.
476 // Note: This is regardless of success as we care about whether a
477 // load attempt is in progress or not. If unsuccessful, a broken
478 // image is still loaded.
479 textureInfo.loadState = UPLOADED;
482 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
484 TextureId textureId = textureInfo.textureId;
486 // If there is an observer: Notify the load is complete, whether successful or not:
487 // And erase it from the list
488 unsigned int observerCount = textureInfo.observerList.Count();
489 TextureInfo* info = &textureInfo;
491 while( observerCount )
493 TextureUploadObserver* observer = info->observerList[0];
495 // During UploadComplete() a Control ResourceReady() signal is emitted
496 // During that signal the app may add remove /add Textures (e.g. via ImageViews).
497 // At this point no more observers can be added to the observerList, because textureInfo.loadState = UPLOADED
498 // However it is possible for observers to be removed, hence we check the observer list count every iteration
500 // Also the reference to the textureInfo struct can become invalidated, because new load requests can modify
501 // the mTextureInfoContainer list (e.g. if more requests are pushed_back it can cause the list to be resized
502 // invalidating the reference to the TextureInfo ).
503 observer->UploadComplete( success, info->textureSet, info->useAtlas, info->atlasRect );
504 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
506 // regrab the textureInfo from the container as it may have been invalidated, if textures have been removed
507 // or added during the ResourceReady() signal emission (from UploadComplete() )
508 int textureInfoIndex = GetCacheIndexFromId( textureId );
510 if( textureInfoIndex == INVALID_CACHE_INDEX)
512 // texture has been removed
515 info = &mTextureInfoContainer[ textureInfoIndex ];
516 observerCount = info->observerList.Count();
517 if ( observerCount > 0 )
519 // remove the observer that was just triggered if it's still in the list
520 for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
524 info->observerList.Erase( j );
536 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
538 return mCurrentTextureId++;
541 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
543 const unsigned int size = mTextureInfoContainer.size();
545 for( unsigned int i = 0; i < size; ++i )
547 if( mTextureInfoContainer[i].textureId == textureId )
553 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
554 return INVALID_CACHE_INDEX;
557 TextureManager::TextureHash TextureManager::GenerateHash(
558 const std::string& url,
559 const ImageDimensions size,
560 const FittingMode::Type fittingMode,
561 const Dali::SamplingMode::Type samplingMode,
562 const UseAtlas useAtlas,
563 TextureId maskTextureId )
565 std::string hashTarget( url );
566 const size_t urlLength = hashTarget.length();
567 const uint16_t width = size.GetWidth();
568 const uint16_t height = size.GetWidth();
570 // If either the width or height has been specified, include the resizing options in the hash
571 if( width != 0 || height != 0 )
573 // We are appending 5 bytes to the URL to form the hash input.
574 hashTarget.resize( urlLength + 5u );
575 char* hashTargetPtr = &( hashTarget[ urlLength ] );
577 // Pack the width and height (4 bytes total).
578 *hashTargetPtr++ = size.GetWidth() & 0xff;
579 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
580 *hashTargetPtr++ = size.GetHeight() & 0xff;
581 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
583 // Bit-pack the FittingMode, SamplingMode and atlasing.
584 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
585 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
589 // We are not including sizing information, but we still need an extra byte for atlasing.
590 hashTarget.resize( urlLength + 1u );
591 // Add the atlasing to the hash input.
592 hashTarget[ urlLength ] = useAtlas;
595 if( maskTextureId != INVALID_TEXTURE_ID )
597 hashTarget.resize( urlLength + sizeof( TextureId ) );
598 TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
600 // Append the hash target to the end of the URL byte by byte:
601 // (to avoid SIGBUS / alignment issues)
602 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
604 *hashTargetPtr++ = maskTextureId & 0xff;
605 maskTextureId >>= 8u;
609 return Dali::CalculateHash( hashTarget );
612 int TextureManager::FindCachedTexture(
613 const TextureManager::TextureHash hash,
614 const std::string& url,
615 const ImageDimensions size,
616 const FittingMode::Type fittingMode,
617 const Dali::SamplingMode::Type samplingMode,
619 TextureId maskTextureId)
621 // Default to an invalid ID, in case we do not find a match.
622 int cacheIndex = INVALID_CACHE_INDEX;
624 // Iterate through our hashes to find a match.
625 const unsigned int count = mTextureInfoContainer.size();
626 for( unsigned int i = 0u; i < count; ++i )
628 if( mTextureInfoContainer[i].hash == hash )
630 // We have a match, now we check all the original parameters in case of a hash collision.
631 TextureInfo& textureInfo( mTextureInfoContainer[i] );
633 if( ( url == textureInfo.url.GetUrl() ) &&
634 ( useAtlas == textureInfo.useAtlas ) &&
635 ( maskTextureId == textureInfo.maskTextureId ) &&
636 ( size == textureInfo.desiredSize ) &&
637 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
638 ( fittingMode == textureInfo.fittingMode &&
639 samplingMode == textureInfo.samplingMode ) ) )
641 // The found Texture is a match.
651 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
653 const unsigned int count = mTextureInfoContainer.size();
654 for( unsigned int i = 0; i < count; ++i )
656 TextureInfo& textureInfo( mTextureInfoContainer[i] );
657 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); ++j )
661 textureInfo.observerList.Erase( j );
668 TextureManager::~TextureManager()
670 mTextureInfoContainer.clear();
671 mAsyncLocalLoadingInfoContainer.clear();
672 mAsyncRemoteLoadingInfoContainer.clear();
678 } // namespace Internal
680 } // namespace Toolkit