Выпускать текстуры (объекты GLKTextureInfo), выделенные GLKTextureLoader

Новое для разработки на iOS и, в частности, новых функций, связанных с OpenGL, на iOS 5, поэтому я приношу свои извинения, если какой-либо из моих основных вопросов является таким основным.

Приложение, над которым я работаю, предназначено для приема кадров камеры и отображения их на экране через OpenGL ES (графические люди возьмут на себя это и добавят фактический рисунок OpenGL, о котором я знаю очень мало). Приложение разработано XCode4, а целью является iPhone4 с iOS 5. На данный момент я использовал функции ARC и GLKit, и все работает отлично, за исключением утечки памяти при загрузке изображений в виде текстуры. Приложение быстро получает "предупреждение о памяти".

В частности, я хотел бы спросить, как выпустить текстуры, выделенные

@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);
    }
}

image - это кварцевое изображение, построенное из кадра камеры (пример кода из яблока). Я знаю, что проблема не в той части кода, поскольку, если я отключу назначение, приложение не получит предупреждение.

Ответ 1

Супер хакерское решение, я считаю, но, похоже, работает:

Добавьте следующее перед назначением:

GLuint name = self.texture.name;
glDeleteTextures(1, &name);

Если есть более официальный способ (или если это официальный путь), я был бы признателен, если бы кто-нибудь мог сообщить мне.

Ответ 2

Не прямой ответ, но что-то, что я заметил, и он не будет действительно вписываться в комментарий.

Если вы используете GLKTextureLoader для загрузки текстур в фоновом режиме для замены существующей текстуры, вам необходимо удалить существующую текстуру в основном потоке. Удаление текстуры в обработчике завершения не будет работать.

AFAIK это потому, что:

  • Для каждого потока iOS требуется собственный EAGLContext, поэтому в фоновом порядке есть собственный поток с собственным контекстом.
  • Обработчик завершения запускается в очереди, в которую вы проходили, что, скорее всего, не является основной. (Иначе вы не будете делать загрузку в фоновом режиме...)

То есть это приведет к утечке памяти.

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;
                                  }];

Чтобы обойти эту проблему, вы можете:

  • Удалить текстуру перед загрузкой. Потенциально отрывочная в зависимости от того, как ваш GL сконструирован.
  • Удалить текстуру в главной очереди в обработчике завершения.

Итак, чтобы исправить утечку, вам нужно сделать это:

//
// 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;
                                  }];

или

//
// 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;
                                    });
                                  }];

Ответ 3

Есть ли способ просто заменить содержимое текстуры на тот же дескриптор GLKTextureInfo.name? При использовании glgentextures вы можете использовать возвращенный обработчик текстуры для загрузки новых данных texuture с помощью glteximage2d. Но с GLKTextureLoader кажется, что glgentextures вызывается каждый раз, когда загружаются новые данные текстур...