Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Release textures (GLKTextureInfo objects) allocated by GLKTextureLoader

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.

like image 605
alokoko Avatar asked Jan 03 '12 23:01

alokoko


2 Answers

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:

  1. Every iOS thread requires its own EAGLContext, so the background queue has its own thread with its own context.
  2. 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:

  1. Delete the texture before the upload happens. Potentially sketchy depending on how your GL is architected.
  2. 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;
                                    });
                                  }];
like image 52
Soup Avatar answered Nov 15 '22 18:11

Soup


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.

like image 20
alokoko Avatar answered Nov 15 '22 20:11

alokoko