I am developing an IPhone OpengGL app using the GLKit, and using the following code to create textures:
NSRange dotRange = [textureFileName rangeOfString:#"." options:NSCaseInsensitiveSearch];
if (dotRange.location == NSNotFound){
NSLog(#"OpenGLDRawMaterial:createTextureFromFileName, incorrect file name given in inputs");
return nil;
}
GLKTextureInfo *newTexture;
NSError *error = nil; // stores the error message if we mess up
NSString *bundlepath = [[NSBundle mainBundle] pathForResource:[textureFileName substringToIndex:dotRange.location]
ofType:[textureFileName substringFromIndex:(dotRange.location+1)]];
newTexture = [GLKTextureLoader textureWithContentsOfFile:bundlepath options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:GLKTextureLoaderOriginBottomLeft] error:&error];
The code works very well , as long as it works in a main thread. Every single time i attempt to make it work in a worker thread i am getting the following message:
"2013-03-04 02:09:01.528 Puppeteer[7063:1503] Error loading texture from image: Error Domain=GLKTextureLoaderErrorDomain Code=17 "The operation couldn’t be completed. (GLKTextureLoaderErrorDomain error 17.)" UserInfo=0x1c5977e0 "
The code i am using for the grand central dispatch queue is :
dispatch_queue_t backgroundQueue = dispatch_queue_create("loadPlayViewBackgroundTexture", 0);
dispatch_async(backgroundQueue, ^{
[self createTexturesForPlayView]; // method calling texture creation
dispatch_async(dispatch_get_main_queue(), ^{
});
});
dispatch_release(backgroundQueue);
If you have any insights or ideas how to fix this issue and have textures being loaded in background i would be very grateful :)
Cheers,
Stéphane
Note that in the documentation for +textureWithContentsOfFile:options:error: includes this statement:check here
This class method loads the texture into the sharegroup attached to the current context for the thread this method is called on.
When you call -textureWithContentsOfFile: from a background thread, that thread has no OpenGL context set (the current GL context is per-thread state), and so GLKit doesn't know which sharegroup to load the texture into.
But, you're making this harder than it needs to be. GLKit can already manage asynchronous texture loading itself. Look at the variant -textureWithContentsOfFile:options:queue:completionHandler:. You don't have to create your own queue at all: you can simply pass in the main queue to receive notification when the load is complete.
Related
I am using the new CoreMotion framework to monitor some of the hardware devices. Here is the typical code to do that:
-(void)startAccelerometer{
self.motion.accelerometerUpdateInterval = 1/30.0f;
NSOperationQueue* accelerometerQueue = [[NSOperationQueue alloc] init];
CMAccelerometerHandler accelerometerHandler = ^(CMAccelerometerData *accelerometerData, NSError *error) {
NSLog(#"Accelerometer realtime values");
NSLog(#"x=%f", accelerometerData.acceleration.x);
NSLog(#"y=%f", accelerometerData.acceleration.y);
NSLog(#"z=%f", accelerometerData.acceleration.z);
NSLog(#" ");
};
[self.motion startAccelerometerUpdatesToQueue:accelerometerQueue withHandler:[[accelerometerHandler copy]autorelease]];
}
That works just fine. Now I want to print the values on a UILabel, but since the CoreMotion frameworks has you use blocks, this is not guaranteed to be in the main queue (and in fact isn't for my app). Is it is "wrong" to just run the label's setter on the main queue like this?
-(void)startAccelerometer{
self.motion.accelerometerUpdateInterval = 1/30.0f;
NSOperationQueue* accelerometerQueue = [[NSOperationQueue alloc] init];
CMAccelerometerHandler accelerometerHandler = ^(CMAccelerometerData *accelerometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
self.lblAccelerometer.text = [NSString stringWithFormat:#"Accelerometer:\nx = %f\ny = %f\nz = %f",
accelerometerData.acceleration.x,
accelerometerData.acceleration.y,
accelerometerData.acceleration.z];
});
};
[self.motion startAccelerometerUpdatesToQueue:accelerometerQueue withHandler:[[accelerometerHandler copy]autorelease]];
}
It works just fine and I don't really see any reason why this would be frowned upon. Any thoughts on that?
This is a common method that I use in many projects. UI updates must occur on the main thread.
//Dispatch on background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//background processing goes here
//Dispatch on main thread
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
});
});
In your case, your UI updates are occurring on the main thread. So I wouldn't worry about changing anything.
You are missunderstanding the concept of blocks, to put it simple:
Blocks are small pieces of code that can be handled as variables and be executed at a certain time or thread.
All UI updates MUST be performed on the main thread so as long as you do this it will be fine.
Codes can be executed in different threads with different priorities in sync or async mode. On your code you are doing it perfectly fine, you not only dispatch it to the Main Queue which is where uiupdates should be executed, but you are also dispatching it async which is the safest way to update send to the main queue (from your code i cannot tell if you are running this specific piece of code from the main queue or a secondary queue but if u were to dispatch a sync block from the main queue to the main queue your program would stop working)
For iOS documentation:
Use the dispatch_get_main_queue function to get the serial dispatch
queue associated with your application’s main thread. This queue is
created automatically for Cocoa applications and for applications that
either call the dispatch_main function or configure a run loop (using
either the CFRunLoopRef type or an NSRunLoop object) on the main
thread.
Read this here http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1
I'm creating and loading a lot of textures (made of strings). To keep the animation running smoothly, I offload the work to a separate worker thread. It seems to work more or less exactly the way I want, but on older devices (iPhone 3GS) I sometimes notice a long (1 sec) lag. It only occurs sometimes. Now I'm wondering if I'm doing this correctly or if there is any conceptual issue. I paste the source code below.
I should also mention that I do not want to use the GLKit TextureLoader because I also want to offload the texture generating work to the other thread, not just the loading part.
In case you're wondering what I need these textures for, have a look at this video: http://youtu.be/U03p4ZhLjvY?hd=1
NSLock* _textureLock;
NSMutableDictionary* _texturesWithString;
NSMutableArray* _texturesWithStringLoading;
// This is called when I request a new texture from the drawing routine.
// If this function returns 0, it means the texture is not ready and Im not displaying it.
-(unsigned int)getTextureWithString:(NSString*)string {
Texture2D* _texture = [_texturesWithString objectForKey:string];
if (_texture==nil){
if (![_texturesWithStringLoading containsObject:string]){
[_texturesWithStringLoading addObject:string];
NSDictionary* dic = [[NSDictionary alloc] initWithObjectsAndKeys:string,#"string", nil];
NSThread* thread = [[NSThread alloc] initWithTarget:self selector:#selector(loadTextureWithDictionary:)object:dic];
thread.threadPriority = 0.01;
[thread start];
[thread release];
}
return 0;
}
return _texture.name;
}
// This is executed on a separate worker thread.
// The lock makes sure that there are not hundreds of separate threads all creating a texture at the same time and therefore slowing everything down.
// There must be a smarter way of doing that. Please let me know if you know how! ;-)
-(void)loadTextureWithOptions:(NSDictionary*)_dic{
[_textureLock lock];
EAGLContext* context = [[SharegroupManager defaultSharegroup] getNewContext];
[EAGLContext setCurrentContext: context];
NSString* string = [_dic objectForKey:#"string"];
Texture2D* _texture = [[Texture2D alloc] initWithStringModified:string];
if (_texture!=nil) {
NSDictionary* _newdic = [[NSDictionary alloc] initWithObjectsAndKeys:_texture,#"texture",string,#"string", nil];
[self performSelectorOnMainThread:#selector(doneLoadingTexture:) withObject:_newdic waitUntilDone:NO];
[_newdic release];
[_texture release];
}
[EAGLContext setCurrentContext: nil];
[context release];
[_textureLock unlock];
}
// This callback is executed on the main thread and marks adds the texture to the texture cache.
-(void)doneLoadingTextureWithDictionary:(NSDictionary*)_dic{
[_texturesWithString setValue:[_dic objectForKey:#"texture"] forKey:[_dic objectForKey:#"string"]];
[_texturesWithStringLoading removeObject:[_dic objectForKey:#"string"]];
}
The problem was that too many threads were started at the same time. Now I am using a NSOperationQueue rather than NSThreads. That allows me to set maxConcurrentOperationCount and only run one extra background thread that does the texture loading.
I want to return information from a turn based game from the game center servers, which is all fine, but I want the player alias which is acquired using the asynchronous method:
[GKPlayer loadPlayersForIdentifiers:singleOpponentArray withCompletionHandler:^(NSArray *players, NSError *error) {
GKPlayer *returnedPlayer = [players objectAtIndex:0];
NSString *aliasToAdd = [NSString stringWithString:returnedPlayer.alias];
NSString *idToAdd = [NSString stringWithString:returnedPlayer.playerID];
NSDictionary *dictionaryToAddToAliasArray = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:aliasToAdd, idToAdd, nil] forKeys:[NSArray arrayWithObjects:#"alias", #"id", nil]];
[self.aliasArray addObject:dictionaryToAddToAliasArray];
}];
But the UI uses this information and it does't arrive in time. How can I make that method execute synchronously on the main thread?
Thanks.
Any UI related code must execute on the main thread.
If your app must wait for the asynchronous call to return, then first disable the UI. For example, set userInteractionEnabled = NO on your UIView.
Then, when the asynchronous methods returns, re-enable the UIView.
In the meantime, display some sort of activity indicator, e.g. UIActivityIndicatorView.
Of course, only do the above in a case where you can't perform the task in the background. Never needlessly block the UI. I'm sure you know that already of course but it's worth restating for any people new to the platform that might be reading this.
To invoke on the main thread, use one of the variants of NSObject's performSelectorOnMainThread method. Or, alternatively, queue it on gcd using the main queue by calling the dispatch_get_main_queue function.
You can do this using GCD functions:
// Show an UILoadingView, etc
[GKPlayer loadPlayersForIdentifiers:singleOpponentArray
withCompletionHandler:^(NSArray *players, NSError *error) {
// Define a block that will do your thing
void (^doTheThing)(void) = ^(void){
// this block will be run in the main thread....
// Stop the UILoadingView and do your thing here
};
// Check the queue this block is called in
dispatch_queue_t main_q = dispatch_get_main_queue();
dispatch_queue_t cur_q = dispatch_get_current_queue();
if (main_q != cur_q) {
// If current block is not called in the main queue change to it and then do your thing
dispatch_async(main_q, doTheThing);
} else {
// If current block is called in the main queue, simply do your thing
doTheThing();
}
}];
In my app I have a tableView filled with contents from a server. To download these contents I use NSURLConnection and I create a NSMutableArray (tableItems) to hold and to manage the addresses to the images I want to use.
In connectionDidFinishLoading, after populating tableItems, there is this for-loop:
for (int i = 0; i < [tableItems count]; i++) {
// HERE I CHECK IF THE IMAGE I'M LOOKING FOR IS ALREADY ON DISK
NSString *pathToCheckImage = [NSString stringWithFormat:#"%#/%#.png", pathToPreviews, [tableItems objectAtIndex:i]];
NSData *dataOfCheckImage = [NSData dataWithContentsOfFile:pathToCheckImage];
UIImage *checkImage = [UIImage imageWithData:dataOfCheckImage];
NSString *pathToImage;
UIImage *image;
if (checkImage != nil) {
// THE IMAGE IS ALREADY ON DISK SO I GET IT FROM TEMP FOLDER
image = checkImage;
} else {
// THE IMAGE IS NEW SO I HAVE TO GET THE IMAGE FROM THE SERVER
pathToImage = [NSString stringWithFormat:#"http://www.SERVER.com/SERVER_PATH/%#.png", [tableItems objectAtIndex:i]];
NSURL *url = [NSURL URLWithString:pathToImage];
NSData *data = [NSData dataWithContentsOfURL:url];
image = [UIImage imageWithData:data];
// AND SAVE IT ON DISK
NSString *path = [NSString stringWithFormat:#"%#/%#.png", pathToPreviews, [tableItems objectAtIndex:i]];
[self cacheImageOnDisk:image withPath:path];
}
if (image != nil) {
[arrayOfImages addObject:image];
}
}
This code is working, even if, depending on the number and size of the images I have to download from the server, it can take 1 or 2 minutes to perform its task.
The problem is that if the user quits (home button pushed) while this for-loop is running it keeps on performing its task until the end, even if it needs 1 minute to finish it.
During this period, launching the app again inevitably ends up crashing on startup.
I've tried to stop this for-loop on exit but applicationDidEnterBackground, applicationWillResignActive and applicationWillTerminate are not called until for-loop ends its task.
I've tried to set "application does not run in background", but nothing changed.
Any suggestion will be really appreciated.
You should not be downloading images on the main thread. The app won't be responsive. Grand Central Dispatch is an easy method to accomplish these tasks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSUInteger count = [tableItems count];
for (int i = 0; i < count; i++) {
// TODO: check if app didResignActive and break
UIImage *image = ... // get image from disk or network
// Add the image to the array on the main thread to avoid any threading issues
dispatch_sync(dispatch_get_main_queue(), ^{
[arrayOfImages addObject:image];
});
}
});
You need to either process your loop off the main thread (very easy to get wrong), or break your work into chunks (much simpler). Try extracting your loop into a separate method that runs it 10 times and then schedules another run with [... performSelector:#selector(myMethod) afterDelay:0]; That will give the runloop a chance to cycle and process events (like quit) every 10 iterations.
Threading (whether via older ways or the newer dispatch_async) will still get you better responsiveness though, so if you want to do that, my recommendation is this:
Share NO data between the background thread and the main thread while it's working.
Spawn your background thread, do your work entirely with local state not accessed anywhere else, then dispatch back to the main thread with your completed array. Any shared state is very likely to make your life very hard.
New to developing on iOS and in particular the new OpenGL related features on iOS 5, so I apologize if any of my questions are so basic.
The app I am working on is designed to receive camera frames and display them on screen via OpenGL ES (the graphic folks will take over this and add the actual OpenGL drawing about which I know very little). The application is developed XCode4, and the target is iPhone4 running iOS 5. For the moment, I used the ARC and the GLKit functionality and all is working fine except for the memory leak in loading the images as texture. The app receives a "memory warning" very soon.
Specifically, I would like to ask how to release the textures allocated by
#property(retain) GLKTextureInfo *texture;
-(void)setTextureCGImage:(CGImageRef)image
{
NSError *error;
self.texture = [GLKTextureLoader textureWithCGImage:image options:nil error:&error];
if (error)
{
NSLog(#"Error loading texture from image: %#",error);
}
}
The image is a quartz image built from the camera frame (sample code from apple). I know the problem is not in that part of the code since if I disable the assignment, the app does not receive the warning.
Super hacky solution I believe, but it seems to work:
Add the following before the assignment:
GLuint name = self.texture.name;
glDeleteTextures(1, &name);
If there's a more official way (or if this is the official way), I would appreciate if someone could let me know.
Not a direct answer, but something I noticed and it wont really fit in a comment.
If you're using GLKTextureLoader to load textures in the background to replace an existing texture, you have to delete the existing texture on the main thread. Deleting a texture in the completion handler will not work.
AFAIK this is because:
Every iOS thread requires its own EAGLContext, so the background queue has its own thread with its own context.
The completion handler is run on the queue you passed in, which is most likely not the main queue. (Else you wouldn't be doing the loading in the background...)
That is, this will leak memory.
NSDictionary *options = #{GLKTextureLoaderOriginBottomLeft:#YES};
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[self.asyncTextureLoader textureWithContentsOfFile:#"my_texture_path.png"
options:options
queue:queue
completionHandler:^(GLKTextureInfo *texture, NSError *e){
GLuint name = self.myTexture.name;
//
// This delete textures call has no effect!!!
//
glDeleteTextures(1, &name);
self.myTexture = texture;
}];
To get around this issue you can either:
Delete the texture before the upload happens. Potentially sketchy depending on how your GL is architected.
Delete the texture on the main queue in the completion handler.
So, to fix the leak you need to do this:
//
// Method #1, delete before upload happens.
// Executed on the main thread so it works as expected.
// Potentially leaves some GL content untextured if you're still drawing it
// while the texture is being loaded in.
//
// Done on the main thread so it works as expected
GLuint name = self.myTexture.name;
glDeleteTextures(1, &name)
NSDictionary *options = #{GLKTextureLoaderOriginBottomLeft:#YES};
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[self.asyncTextureLoader textureWithContentsOfFile:#"my_texture_path.png"
options:options
queue:queue
completionHandler:^(GLKTextureInfo *texture, NSError *e){
// no delete required, done previously.
self.myTexture = texture;
}];
or
//
// Method #2, delete in completion handler but do it on the main thread.
//
NSDictionary *options = #{GLKTextureLoaderOriginBottomLeft:#YES};
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[self.asyncTextureLoader textureWithContentsOfFile:#"my_texture_path.png"
options:options
queue:queue
completionHandler:^(GLKTextureInfo *texture, NSError *e){
// you could potentially do non-gl related work here, still in the background
// ...
// Force the actual texture delete and re-assignment to happen on the main thread.
dispatch_sync(dispatch_get_main_queue(), ^{
GLuint name = self.myTexture.name;
glDeleteTextures(1, &name);
self.myTexture = texture;
});
}];
Is there a way to simply replace the contents of the texture to the same GLKTextureInfo.name handle? When using glgentextures you can use the returned texture handle to load new texuture data using glteximage2d. But with GLKTextureLoader it seems that glgentextures is being called every time new texture data is loaded...