Skip to content

Commit

Permalink
Texture & TextureCache: uses current thread and cocos2d thread
Browse files Browse the repository at this point in the history
Texture2D: uses current thread for glDeleteTexture()
TextureCache: uses cocos2d thread to invoke async callback()

These bugs were causing kernel panics on Mac
  • Loading branch information
ricardoquesada committed Feb 25, 2012
1 parent d92cdff commit b6c6cb4
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 50 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Expand Up @@ -57,6 +57,8 @@ version 2.0-rc0 XX-Feb-2012
. [FIX] Texture2D: initWithImage renamed with initWithCGImage
JPEG: No need to convert JPG to PNG on iOS4 (issue #886 re-fixed)
BMP: Images are displayed on iOS4 like on iOS5 (without alpha channel) [iOS5 BUG]
'Delete texture' is executed on current thread (not on main thread)
. [FIX] TextureCache: async invokes callback on cocos2d thread (and not on main thread)
. [FIX] Types: kCCResolutionStandard renamed to kCCResolutioniPhone

version 2.0-beta2 17-Jan-2012
Expand Down
8 changes: 8 additions & 0 deletions cocos2d-ios.xcodeproj/project.pbxproj
Expand Up @@ -1746,6 +1746,8 @@
A0C20AC9144FDAF700D84B47 /* CCParticleBatchNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A0C20AC7144FDAF700D84B47 /* CCParticleBatchNode.m */; };
A0C3815114BA0548001655CC /* CCTransitionProgress.h in Headers */ = {isa = PBXBuildFile; fileRef = A0C3814F14BA0548001655CC /* CCTransitionProgress.h */; };
A0C3815214BA0548001655CC /* CCTransitionProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = A0C3815014BA0548001655CC /* CCTransitionProgress.m */; };
A0C87D1A14F9A3A100C0E8B2 /* NSThread+performBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = A0C87D1814F9A3A100C0E8B2 /* NSThread+performBlock.h */; };
A0C87D1B14F9A3A100C0E8B2 /* NSThread+performBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = A0C87D1914F9A3A100C0E8B2 /* NSThread+performBlock.m */; };
A0D3A8FF149D2FAE0077CD93 /* BaseAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = A0D3A8FE149D2FA40077CD93 /* BaseAppController.m */; };
A0D3A900149D2FAE0077CD93 /* BaseAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = A0D3A8FE149D2FA40077CD93 /* BaseAppController.m */; };
A0D3A901149D2FAE0077CD93 /* BaseAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = A0D3A8FE149D2FA40077CD93 /* BaseAppController.m */; };
Expand Down Expand Up @@ -4905,6 +4907,8 @@
A0C20AC7144FDAF700D84B47 /* CCParticleBatchNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCParticleBatchNode.m; sourceTree = "<group>"; };
A0C3814F14BA0548001655CC /* CCTransitionProgress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCTransitionProgress.h; sourceTree = "<group>"; };
A0C3815014BA0548001655CC /* CCTransitionProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCTransitionProgress.m; sourceTree = "<group>"; };
A0C87D1814F9A3A100C0E8B2 /* NSThread+performBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSThread+performBlock.h"; sourceTree = "<group>"; };
A0C87D1914F9A3A100C0E8B2 /* NSThread+performBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSThread+performBlock.m"; sourceTree = "<group>"; };
A0D3A8FD149D2F9D0077CD93 /* BaseAppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BaseAppController.h; path = tests/BaseAppController.h; sourceTree = "<group>"; };
A0D3A8FE149D2FA40077CD93 /* BaseAppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BaseAppController.m; path = tests/BaseAppController.m; sourceTree = "<group>"; };
A0D77A0E146E3244000960C7 /* markerFelt-hd.fnt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "markerFelt-hd.fnt"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6163,6 +6167,8 @@
50F29F5510204FD60046CA73 /* base64.c */,
50F2A103102094550046CA73 /* ZipUtils.h */,
50F2A102102094550046CA73 /* ZipUtils.m */,
A0C87D1814F9A3A100C0E8B2 /* NSThread+performBlock.h */,
A0C87D1914F9A3A100C0E8B2 /* NSThread+performBlock.m */,
);
path = Support;
sourceTree = "<group>";
Expand Down Expand Up @@ -7838,6 +7844,7 @@
A046E29914C1DB7E0005BBF2 /* CCWindow.h in Headers */,
A09AB76914EA12F7009C8B91 /* ccDeprecated.h in Headers */,
A0A10B5714F3011F00FA38C1 /* ccGLStateCache.h in Headers */,
A0C87D1A14F9A3A100C0E8B2 /* NSThread+performBlock.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -12046,6 +12053,7 @@
A046E29A14C1DB7E0005BBF2 /* CCWindow.m in Sources */,
A09AB76A14EA12F7009C8B91 /* ccDeprecated.m in Sources */,
A0A10B5814F3011F00FA38C1 /* ccGLStateCache.m in Sources */,
A0C87D1B14F9A3A100C0E8B2 /* NSThread+performBlock.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
14 changes: 14 additions & 0 deletions cocos2d-mac.xcodeproj/project.pbxproj
Expand Up @@ -1984,6 +1984,7 @@
A0C491EC146F8CA500C4C037 /* arrows.png in Resources */ = {isa = PBXBuildFile; fileRef = A0C491E8146F8CA500C4C037 /* arrows.png */; };
A0C491ED146F8CA500C4C037 /* arrowsBar-hd.png in Resources */ = {isa = PBXBuildFile; fileRef = A0C491E9146F8CA500C4C037 /* arrowsBar-hd.png */; };
A0C491EE146F8CA500C4C037 /* arrowsBar.png in Resources */ = {isa = PBXBuildFile; fileRef = A0C491EA146F8CA500C4C037 /* arrowsBar.png */; };
A0C879AD14F98F1C00C0E8B2 /* NSThread+performBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = A0C879AC14F98F1C00C0E8B2 /* NSThread+performBlock.m */; };
A0DC2F611491B3FA005B814E /* test_image_rgb888.pvr in Resources */ = {isa = PBXBuildFile; fileRef = A0DC2F601491B3FA005B814E /* test_image_rgb888.pvr */; };
A0E95100142698D2009F9607 /* grossini_pvr_rgba8888.pvr in Resources */ = {isa = PBXBuildFile; fileRef = A0E950FF142698D2009F9607 /* grossini_pvr_rgba8888.pvr */; };
A0EC4D801403278300EC482A /* Position_uColor.fsh in Resources */ = {isa = PBXBuildFile; fileRef = A0EC4CAF1403278200EC482A /* Position_uColor.fsh */; };
Expand Down Expand Up @@ -5905,6 +5906,8 @@
A0C491E8146F8CA500C4C037 /* arrows.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = arrows.png; sourceTree = "<group>"; };
A0C491E9146F8CA500C4C037 /* arrowsBar-hd.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "arrowsBar-hd.png"; sourceTree = "<group>"; };
A0C491EA146F8CA500C4C037 /* arrowsBar.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = arrowsBar.png; sourceTree = "<group>"; };
A0C879AB14F98F0200C0E8B2 /* NSThread+performBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSThread+performBlock.h"; sourceTree = "<group>"; };
A0C879AC14F98F1C00C0E8B2 /* NSThread+performBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSThread+performBlock.m"; sourceTree = "<group>"; };
A0DC2F601491B3FA005B814E /* test_image_rgb888.pvr */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_image_rgb888.pvr; sourceTree = "<group>"; };
A0E55417141EAEEE003F1E5A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
A0E950FF142698D2009F9607 /* grossini_pvr_rgba8888.pvr */ = {isa = PBXFileReference; lastKnownFileType = file; path = grossini_pvr_rgba8888.pvr; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7464,6 +7467,8 @@
E076E6CC1225EC7400DE0DA2 /* utlist.h */,
E076E6CD1225EC7400DE0DA2 /* ZipUtils.h */,
E076E6CE1225EC7400DE0DA2 /* ZipUtils.m */,
A0C879AB14F98F0200C0E8B2 /* NSThread+performBlock.h */,
A0C879AC14F98F1C00C0E8B2 /* NSThread+performBlock.m */,
);
path = Support;
sourceTree = "<group>";
Expand Down Expand Up @@ -13829,6 +13834,7 @@
A046E2BD14C1E5000005BBF2 /* CCTouchHandler.m in Sources */,
A046E2C114C1E5480005BBF2 /* CCGLProgram.m in Sources */,
A0A10B2114F3003600FA38C1 /* ccDeprecated.m in Sources */,
A0C879AD14F98F1C00C0E8B2 /* NSThread+performBlock.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -14791,6 +14797,10 @@
);
MACOSX_DEPLOYMENT_TARGET = 10.6;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = (
"-ObjC",
"-all_load",
);
SDKROOT = macosx;
};
name = Debug;
Expand Down Expand Up @@ -14818,6 +14828,10 @@
external/kazmath/include,
);
MACOSX_DEPLOYMENT_TARGET = 10.6;
OTHER_LDFLAGS = (
"-ObjC",
"-all_load",
);
SDKROOT = macosx;
};
name = Release;
Expand Down
8 changes: 3 additions & 5 deletions cocos2d/CCTexture2D.m
Expand Up @@ -80,6 +80,7 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
#import "CCGLProgram.h"
#import "ccGLStateCache.h"
#import "CCShaderCache.h"
#import "CCDirector.h"

#import "Support/ccUtils.h"
#import "Support/CCFileUtils.h"
Expand Down Expand Up @@ -188,12 +189,9 @@ - (void) dealloc

if( name_ ) {

GLuint name = name_;
NSAssert( [[CCDirector sharedDirector] runningThread] == [NSThread currentThread], @"cocos2d: Shall not happen. Please report this bug on the cocos2d issue tracker" );

//It is very likely dealloc will get called from the texture cache's dictionary thread but this must be run from the cocos2d thread
dispatch_async(dispatch_get_main_queue(), ^(void) {
ccGLDeleteTexture( name );
});
ccGLDeleteTexture( name_ );
}

[super dealloc];
Expand Down
52 changes: 10 additions & 42 deletions cocos2d/CCTextureCache.m
Expand Up @@ -30,11 +30,14 @@
#import "CCTexture2D.h"
#import "CCTexturePVR.h"
#import "CCConfiguration.h"
#import "Support/CCFileUtils.h"
#import "CCDirector.h"
#import "ccConfig.h"
#import "ccTypes.h"

#import "Support/CCFileUtils.h"
#import "Support/NSThread+performBlock.h"


#ifdef __CC_PLATFORM_MAC
#import "Platforms/Mac/CCDirectorMac.h"
#endif
Expand Down Expand Up @@ -175,9 +178,7 @@ -(void) addImageAsync: (NSString*)path target:(id)target selector:(SEL)selector
glFlush();

// callback should be executed in cocos2d thread
dispatch_async(dispatch_get_main_queue(), ^{
[target performSelector:selector withObject:texture];
});
[target performSelector:selector onThread:[[CCDirector sharedDirector] runningThread] withObject:texture waitUntilDone:NO];

[EAGLContext setCurrentContext:nil];
} else {
Expand All @@ -194,9 +195,7 @@ -(void) addImageAsync: (NSString*)path target:(id)target selector:(SEL)selector
glFlush();

// callback should be executed in cocos2d thread
dispatch_async(dispatch_get_main_queue(), ^{
[target performSelector:selector withObject:texture];
});
[target performSelector:selector onThread:[[CCDirector sharedDirector] runningThread] withObject:texture waitUntilDone:NO];

[NSOpenGLContext clearCurrentContext];

Expand Down Expand Up @@ -240,9 +239,8 @@ -(void) addImageAsync:(NSString*)path withBlock:(void(^)(CCTexture2D *tex))block
glFlush();

// callback should be executed in cocos2d thread
dispatch_async(dispatch_get_main_queue(), ^{
block(texture);
});
NSThread *thread = [[CCDirector sharedDirector] runningThread];
[thread performBlock:block withObject:texture waitUntilDone:NO];

[EAGLContext setCurrentContext:nil];
} else {
Expand All @@ -259,9 +257,8 @@ -(void) addImageAsync:(NSString*)path withBlock:(void(^)(CCTexture2D *tex))block
glFlush();

// callback should be executed in cocos2d thread
dispatch_async(dispatch_get_main_queue(), ^{
block(texture);
});
NSThread *thread = [[CCDirector sharedDirector] runningThread];
[thread performBlock:block withObject:texture waitUntilDone:NO];

[NSOpenGLContext clearCurrentContext];

Expand Down Expand Up @@ -296,35 +293,6 @@ -(CCTexture2D*) addImage: (NSString*) path

#ifdef __CC_PLATFORM_IOS

// Issue #886: TEMPORARY FIX FOR TRANSPARENT JPEGS IN IOS4
// else if ( ([[CCConfiguration sharedConfiguration] OSVersion] < kCCiOSVersion_5_0) &&
// ( [lowerCase hasSuffix:@".jpg"] || [lowerCase hasSuffix:@".jpeg"] )
// ) {
// // convert jpg to png before loading the texture
//
// CCLOG(@"cocos2d: WARNING: Loading JPEG image. For faster loading times, convert it to PVR or PNG. See issue #886");
//
// ccResolutionType resolution;
// NSString *fullpath = [CCFileUtils fullPathFromRelativePath:path resolutionType:&resolution];
//
// UIImage *jpg = [[UIImage alloc] initWithContentsOfFile:fullpath];
// UIImage *png = [[UIImage alloc] initWithData:UIImagePNGRepresentation(jpg)];
// tex = [ [CCTexture2D alloc] initWithCGImage:png.CGImage resolutionType:resolution];
// [png release];
// [jpg release];
//
// if( tex ){
// dispatch_sync(_dictQueue, ^{
// [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 {

ccResolutionType resolution;
Expand Down
22 changes: 22 additions & 0 deletions cocos2d/Support/NSThread+performBlock.h
@@ -0,0 +1,22 @@
/* cocos2d for iPhone
*
* http://www.cocos2d-iphone.org
*
*
* Idea taken from: http://stackoverflow.com/a/3940757
*
*/

#import <Foundation/Foundation.h>

@interface NSThread (sendBlockToBackground)
/** performs a block on the thread. It won't wait until it is done. */
- (void) performBlock:(void (^)(void))block;

/** performs a block on the thread. */
- (void) performBlock:(void (^)(void))block waitUntilDone:(BOOL)wait;

/** performs a block on the thread. */
- (void) performBlock:(void (^)(id param))block withObject:(id)object waitUntilDone:(BOOL)wait;

@end
76 changes: 76 additions & 0 deletions cocos2d/Support/NSThread+performBlock.m
@@ -0,0 +1,76 @@
/* cocos2d for iPhone
*
* http://www.cocos2d-iphone.org
*
*
* Idea taken from: http://stackoverflow.com/a/3940757
*
*/


#import "NSThread+performBlock.h"
#import "../ccMacros.h"

typedef void (^BlockWithParam)(id param);
@interface CCObjectWith2Params : NSObject
{
@public
BlockWithParam block;
id param;
}
@property (nonatomic,copy) id block;
@property (nonatomic,readwrite,retain) id param;
@end

@implementation CCObjectWith2Params
@synthesize block, param;
- (void)dealloc {
CCLOG(@"cocos2d: deallocing %@", self);
[block release];
[param release];

[super dealloc];
}
@end

@implementation NSThread (sendBlockToBackground)

- (void) performBlock: (void (^)(void))block;
{
return [self performBlock:block waitUntilDone:NO];
}

- (void) performBlock:(void (^)(void))block waitUntilDone:(BOOL)wait
{
[self performSelector:@selector(executeBlock:)
onThread:self
withObject: block
waitUntilDone: wait];
}

- (void) performBlock:(void (^)(id param))block withObject:(id)object waitUntilDone:(BOOL)wait
{
CCObjectWith2Params * obj = [[CCObjectWith2Params alloc] init];
obj.block = block;
obj.param = object;

[obj autorelease];

[self performSelector:@selector(executeBlock2:)
onThread:self
withObject:obj
waitUntilDone:wait];
}

- (void) executeBlock: (void (^)(void))block;
{
block();
}

- (void) executeBlock2:(CCObjectWith2Params*)object
{
BlockWithParam block = object.block;
block( object.param );
}

@end
16 changes: 13 additions & 3 deletions tests/Texture2dTest.m
Expand Up @@ -19,6 +19,9 @@
static int sceneIdx=-1;
static NSString *transitions[] = {

@"TextureAsync",
@"TextureAsyncBlock",

@"TextureAlias",
@"TextureMipMap",
@"TexturePVRMipMap",
Expand Down Expand Up @@ -1224,6 +1227,10 @@ -(void) loadImages:(ccTime) dt

-(void) imageLoaded: (CCTexture2D*) tex
{
CCDirector *director = [CCDirector sharedDirector];

NSAssert( [NSThread currentThread] == [director runningThread], @"FAIL. Callback should be on cocos2d thread");

// IMPORTANT: The order on the callback is not guaranteed. Don't depend on the callback

// This test just creates a sprite based on the Texture
Expand All @@ -1232,8 +1239,7 @@ -(void) imageLoaded: (CCTexture2D*) tex
sprite.anchorPoint = ccp(0,0);
[self addChild:sprite z:-1];

CGSize size =[[CCDirector sharedDirector] winSize];

CGSize size = [director winSize];
int i = imageOffset * 32;
sprite.position = ccp( i % (int)size.width, (i / (int)size.width) * 32 );

Expand Down Expand Up @@ -1294,13 +1300,17 @@ -(void) loadImages:(ccTime) dt

void(^block)(CCTexture2D *block) = ^(CCTexture2D* tex){

CCDirector *director = [CCDirector sharedDirector];

NSAssert( [NSThread currentThread] == [director runningThread], @"FAIL. Callback should be on cocos2d thread");

CCSprite *sprite = [CCSprite spriteWithTexture:tex];
sprite.anchorPoint = ccp(0,0);
[self addChild:sprite z:-1];

CGSize size =[[CCDirector sharedDirector] winSize];

int i = imageOffset * 32;
CGSize size = [director winSize];
sprite.position = ccp( i % (int)size.width, (i / (int)size.width) * 32 );

imageOffset++;
Expand Down

0 comments on commit b6c6cb4

Please sign in to comment.