/* * cocos2d for iPhone: http://www.cocos2d-iphone.org * * Copyright (c) 2008-2010 Ricardo Quesada * Copyright (c) 2011 Zynga Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #import #import "Platforms/CCGL.h" #import "CCTextureCache.h" #import "CCTexture2D.h" #import "CCTexturePVR.h" #import "ccMacros.h" #import "CCConfiguration.h" #import "Support/CCFileUtils.h" #import "CCDirector.h" #import "ccConfig.h" // needed for CCCallFuncO in Mac-display_link version #import "CCActionManager.h" #import "CCActionInstant.h" #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED static EAGLContext *auxGLcontext = nil; #elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) static NSOpenGLContext *auxGLcontext = nil; #endif @interface CCAsyncObject : NSObject { SEL selector_; id target_; id data_; } @property (readwrite,assign) SEL selector; @property (readwrite,retain) id target; @property (readwrite,retain) id data; @end @implementation CCAsyncObject @synthesize selector = selector_; @synthesize target = target_; @synthesize data = data_; - (void) dealloc { CCLOGINFO(@"cocos2d: deallocing %@", self); [target_ release]; [data_ release]; [super dealloc]; } @end @implementation CCTextureCache #pragma mark TextureCache - Alloc, Init & Dealloc static CCTextureCache *sharedTextureCache; + (CCTextureCache *)sharedTextureCache { if (!sharedTextureCache) sharedTextureCache = [[CCTextureCache alloc] init]; return sharedTextureCache; } +(id)alloc { NSAssert(sharedTextureCache == nil, @"Attempted to allocate a second instance of a singleton."); return [super alloc]; } +(void)purgeSharedTextureCache { [sharedTextureCache release]; sharedTextureCache = nil; } -(id) init { if( (self=[super init]) ) { textures_ = [[NSMutableDictionary dictionaryWithCapacity: 10] retain]; dictLock_ = [[NSLock alloc] init]; contextLock_ = [[NSLock alloc] init]; } return self; } - (NSString*) description { return [NSString stringWithFormat:@"<%@ = %08X | num of textures = %i | keys: %@>", [self class], self, [textures_ count], [textures_ allKeys] ]; } -(void) dealloc { CCLOGINFO(@"cocos2d: deallocing %@", self); [textures_ release]; [dictLock_ release]; [contextLock_ release]; [auxGLcontext release]; auxGLcontext = nil; sharedTextureCache = nil; [super dealloc]; } #pragma mark TextureCache - Add Images -(void) addImageWithAsyncObject:(CCAsyncObject*)async { NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED // textures will be created on the main OpenGL context // it seems that in SDK 2.2.x there can't be 2 threads creating textures at the same time // the lock is used for this purpose: issue #472 [contextLock_ lock]; if( auxGLcontext == nil ) { auxGLcontext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1 sharegroup:[[[[CCDirector sharedDirector] openGLView] context] sharegroup]]; if( ! auxGLcontext ) CCLOG(@"cocos2d: TextureCache: Could not create EAGL context"); } if( [EAGLContext setCurrentContext:auxGLcontext] ) { // load / create the texture CCTexture2D *tex = [self addImage:async.data]; // The callback will be executed on the main thread [async.target performSelectorOnMainThread:async.selector withObject:tex waitUntilDone:NO]; [EAGLContext setCurrentContext:nil]; } else { CCLOG(@"cocos2d: TetureCache: EAGLContext error"); } [contextLock_ unlock]; [autoreleasepool release]; #elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) [contextLock_ lock]; if( auxGLcontext == nil ) { MacGLView *view = [[CCDirector sharedDirector] openGLView]; NSOpenGLPixelFormat *pf = [view pixelFormat]; NSOpenGLContext *share = [view openGLContext]; auxGLcontext = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:share]; if( ! auxGLcontext ) CCLOG(@"cocos2d: TextureCache: Could not create NSOpenGLContext"); } [auxGLcontext makeCurrentContext]; // load / create the texture CCTexture2D *tex = [self addImage:async.data]; #if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD id action = [CCCallFuncO actionWithTarget:async.target selector:async.selector object:tex]; [[CCActionManager sharedManager] addAction:action target:async.target paused:NO]; #else // The callback will be executed on the main thread [async.target performSelector:async.selector onThread:[[CCDirector sharedDirector] runningThread] withObject:tex waitUntilDone:NO]; #endif [NSOpenGLContext clearCurrentContext]; [contextLock_ unlock]; [autoreleasepool release]; #endif // __MAC_OS_X_VERSION_MAX_ALLOWED } -(void) addImageAsync: (NSString*)path target:(id)target selector:(SEL)selector { NSAssert(path != nil, @"TextureCache: fileimage MUST not be nill"); // optimization CCTexture2D * tex; path = ccRemoveHDSuffixFromFile(path); if( (tex=[textures_ objectForKey: path] ) ) { [target performSelector:selector withObject:tex]; return; } // schedule the load CCAsyncObject *asyncObject = [[CCAsyncObject alloc] init]; asyncObject.selector = selector; asyncObject.target = target; asyncObject.data = path; [NSThread detachNewThreadSelector:@selector(addImageWithAsyncObject:) toTarget:self withObject:asyncObject]; [asyncObject release]; } -(CCTexture2D*) addImage: (NSString*) path { NSAssert(path != nil, @"TextureCache: fileimage MUST not be nill"); CCTexture2D * tex = nil; // MUTEX: // Needed since addImageAsync calls this method from a different thread [dictLock_ lock]; // remove possible -HD suffix to prevent caching the same image twice (issue #1040) path = ccRemoveHDSuffixFromFile( path ); tex=[textures_ objectForKey: path]; if( ! tex ) { NSString *lowerCase = [path lowercaseString]; // all images are handled by UIImage except PVR extension that is handled by our own handler if ( [lowerCase hasSuffix:@".pvr"] || [lowerCase hasSuffix:@".pvr.gz"] || [lowerCase hasSuffix:@".pvr.ccz"] ) tex = [self addPVRImage:path]; // Only iPhone #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED // Issue #886: TEMPORARY FIX FOR TRANSPARENT JPEGS IN IOS4 else if ( ( [[CCConfiguration sharedConfiguration] OSVersion] >= kCCiOSVersion_4_0) && ( [lowerCase hasSuffix:@".jpg"] || [lowerCase hasSuffix:@".jpeg"] ) ) { // convert jpg to png before loading the texture NSString *fullpath = [CCFileUtils fullPathFromRelativePath: path ]; UIImage *jpg = [[UIImage alloc] initWithContentsOfFile:fullpath]; UIImage *png = [[UIImage alloc] initWithData:UIImagePNGRepresentation(jpg)]; tex = [ [CCTexture2D alloc] initWithImage: png ]; [png release]; [jpg release]; if( tex ) [textures_ setObject: tex forKey:path]; else CCLOG(@"cocos2d: Couldn't add image:%@ in CCTextureCache", path); // autorelease prevents possible crash in multithreaded environments [tex autorelease]; } else { // prevents overloading the autorelease pool NSString *fullpath = [CCFileUtils fullPathFromRelativePath: path ]; UIImage *image = [ [UIImage alloc] initWithContentsOfFile: fullpath ]; tex = [ [CCTexture2D alloc] initWithImage: image ]; [image release]; if( tex ) [textures_ setObject: tex forKey:path]; else CCLOG(@"cocos2d: Couldn't add image:%@ in CCTextureCache", path); // autorelease prevents possible crash in multithreaded environments [tex autorelease]; } // Only in Mac #elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) else { NSString *fullpath = [CCFileUtils fullPathFromRelativePath: path ]; NSData *data = [[NSData alloc] initWithContentsOfFile:fullpath]; NSBitmapImageRep *image = [[NSBitmapImageRep alloc] initWithData:data]; tex = [ [CCTexture2D alloc] initWithImage:[image CGImage]]; [data release]; [image release]; if( tex ) [textures_ setObject: tex forKey:path]; else CCLOG(@"cocos2d: Couldn't add image:%@ in CCTextureCache", path); // autorelease prevents possible crash in multithreaded environments [tex autorelease]; } #endif // __MAC_OS_X_VERSION_MAX_ALLOWED } [dictLock_ unlock]; return tex; } -(CCTexture2D*) addCGImage: (CGImageRef) imageref forKey: (NSString *)key { NSAssert(imageref != nil, @"TextureCache: image MUST not be nill"); CCTexture2D * tex = nil; // If key is nil, then create a new texture each time if( key && (tex=[textures_ objectForKey: key] ) ) { return tex; } #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED // prevents overloading the autorelease pool UIImage *image = [[UIImage alloc] initWithCGImage:imageref]; tex = [[CCTexture2D alloc] initWithImage: image]; [image release]; #elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) tex = [[CCTexture2D alloc] initWithImage: imageref]; #endif if(tex && key) [textures_ setObject: tex forKey:key]; else CCLOG(@"cocos2d: Couldn't add CGImage in CCTextureCache"); return [tex autorelease]; } #pragma mark TextureCache - Remove -(void) removeAllTextures { [textures_ removeAllObjects]; } -(void) removeUnusedTextures { NSArray *keys = [textures_ allKeys]; for( id key in keys ) { id value = [textures_ objectForKey:key]; if( [value retainCount] == 1 ) { CCLOG(@"cocos2d: CCTextureCache: removing unused texture: %@", key); [textures_ removeObjectForKey:key]; } } } -(void) removeTexture: (CCTexture2D*) tex { if( ! tex ) return; NSArray *keys = [textures_ allKeysForObject:tex]; for( NSUInteger i = 0; i < [keys count]; i++ ) [textures_ removeObjectForKey:[keys objectAtIndex:i]]; } -(void) removeTextureForKey:(NSString*)name { if( ! name ) return; [textures_ removeObjectForKey:name]; } #pragma mark TextureCache - Get - (CCTexture2D *)textureForKey:(NSString *)key { return [textures_ objectForKey:key]; } @end @implementation CCTextureCache (PVRSupport) #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED -(CCTexture2D*) addPVRTCImage:(NSString*)path bpp:(int)bpp hasAlpha:(BOOL)alpha width:(int)w { NSAssert(path != nil, @"TextureCache: fileimage MUST not be nill"); NSAssert( bpp==2 || bpp==4, @"TextureCache: bpp must be either 2 or 4"); CCTexture2D * tex; // remove possible -HD suffix to prevent caching the same image twice (issue #1040) path = ccRemoveHDSuffixFromFile( path ); if( (tex=[textures_ objectForKey: path] ) ) { return tex; } // Split up directory and filename NSString *fullpath = [CCFileUtils fullPathFromRelativePath:path]; NSData *nsdata = [[NSData alloc] initWithContentsOfFile:fullpath]; tex = [[CCTexture2D alloc] initWithPVRTCData:[nsdata bytes] level:0 bpp:bpp hasAlpha:alpha length:w pixelFormat:bpp==2?kCCTexture2DPixelFormat_PVRTC2:kCCTexture2DPixelFormat_PVRTC4]; if( tex ) [textures_ setObject: tex forKey:path]; else CCLOG(@"cocos2d: Couldn't add PVRTCImage:%@ in CCTextureCache",path); [nsdata release]; return [tex autorelease]; } #endif // __IPHONE_OS_VERSION_MAX_ALLOWED -(CCTexture2D*) addPVRImage:(NSString*)path { NSAssert(path != nil, @"TextureCache: fileimage MUST not be nill"); CCTexture2D * tex; // remove possible -HD suffix to prevent caching the same image twice (issue #1040) path = ccRemoveHDSuffixFromFile( path ); if( (tex=[textures_ objectForKey: path] ) ) { return tex; } // Split up directory and filename NSString *fullpath = [CCFileUtils fullPathFromRelativePath:path]; tex = [[CCTexture2D alloc] initWithPVRFile: fullpath]; if( tex ) [textures_ setObject: tex forKey:path]; else CCLOG(@"cocos2d: Couldn't add PVRImage:%@ in CCTextureCache",path); return [tex autorelease]; } @end @implementation CCTextureCache (Debug) -(void) dumpCachedTextureInfo { NSUInteger count = 0; NSUInteger totalBytes = 0; for (NSString* texKey in textures_) { CCTexture2D* tex = [textures_ objectForKey:texKey]; NSUInteger bpp = [tex bitsPerPixelForFormat]; // Each texture takes up width * height * bytesPerPixel bytes. NSUInteger bytes = tex.pixelsWide * tex.pixelsWide * bpp / 8; totalBytes += bytes; count++; CCLOG( @"cocos2d: \"%@\" rc=%lu id=%lu %lu x %lu @ %ld bpp => %lu KB", texKey, (long)[tex retainCount], (long)tex.name, (long)tex.pixelsWide, (long)tex.pixelsHigh, (long)bpp, (long)bytes / 1024 ); } CCLOG( @"cocos2d: CCTextureCache dumpDebugInfo: %ld textures, for %lu KB (%.2f MB)", (long)count, (long)totalBytes / 1024, totalBytes / (1024.0f*1024.0f)); } @end