0

I am having trouble with an app with an OpenGL component crashing on iPad. The app throws a memory warning and crashes, but it doesn't appear to be using that much memory. Am I missing something?

The app is based on the Vuforia augmented reality system (borrows heavily from the ImageTargets sample). I have about 40 different models I need to include in my app, so in the interests of memory conservation I am loading the objects (and rendering textures etc) dynamically in the app as I need them. I tried to copy the UIScrollView lazy loading idea. The three 4mb allocations are the textures I have loaded into memory ready for when the user selects a different model to display.

Anything odd in here?

Instruments - leaks

I don't know much at all about OpenGL (part of the reason why I chose the Vurforia engine). Anything in this screen shot below that should concern me? Note that Vurforia's ImageTagets sample app also has Uninitialized Texture Data (about one per frame), so I don't think this is the problem.

Instruments - OpenGL ES analyzer

Any help would be appreciated!!

Here is the code that generates the 3D objects (in EAGLView):

// Load the textures for use by OpenGL
-(void)loadATexture:(int)texNumber {

if (texNumber >= 0 && texNumber < [tempTextureList count]) {
    currentlyChangingTextures = YES;

    [textureList removeAllObjects];
    [textureList addObject:[tempTextureList objectAtIndex:texNumber]];

    Texture *tex = [[Texture alloc] init];
    NSString *file = [textureList objectAtIndex:0];

    [tex loadImage:file];



    [textures replaceObjectAtIndex:texNumber withObject:tex];
    [tex release];

    // Remove all old textures outside of the one we're interested in and the two on either side of the picker.
    for (int i = 0; i < [textures count]; ++i) {

        if (i < targetIndex - 1 || i > targetIndex + 1) {
            [textures replaceObjectAtIndex:i withObject:@""];
        }
    }


    // Render - Generate the OpenGL texture objects
    GLuint nID;
    Texture *texture = [textures objectAtIndex:texNumber];
    glGenTextures(1, &nID);
    [texture setTextureID: nID];
    glBindTexture(GL_TEXTURE_2D, nID);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, [texture width], [texture height], 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)[texture pngData]);


    // Set up objects using the above textures.
    Object3D *obj3D = [[Object3D alloc] init];

    obj3D.numVertices = rugNumVerts;
    obj3D.vertices = rugVerts;
    obj3D.normals = rugNormals;
    obj3D.texCoords = rugTexCoords;

    obj3D.texture = [textures objectAtIndex:texNumber];

    [objects3D replaceObjectAtIndex:texNumber withObject:obj3D];
    [obj3D release];

    // Remove all objects except the one currently visible and the ones on either side of the picker.
    for (int i = 0; i < [tempTextureList count]; ++i) {

        if (i < targetIndex - 1 || i > targetIndex + 1) {
            Object3D *obj3D = [[Object3D alloc] init];
            [objects3D replaceObjectAtIndex:i withObject:obj3D];
            [obj3D release];
        }
    }


    if (QCAR::GL_20 & qUtils.QCARFlags) {
        [self initShaders];
    }


    currentlyChangingTextures = NO;
}

}

Here is the code in the textures object.

- (id)init
{
self = [super init];
pngData = NULL;

return self;
}


- (BOOL)loadImage:(NSString*)filename
{
BOOL ret = NO;

// Build the full path of the image file
NSString* resourcePath = [[NSBundle mainBundle] resourcePath];
NSString* fullPath = [resourcePath stringByAppendingPathComponent:filename];

// Create a UIImage with the contents of the file
UIImage* uiImage = [UIImage imageWithContentsOfFile:fullPath];

if (uiImage) {
    // Get the inner CGImage from the UIImage wrapper
    CGImageRef cgImage = uiImage.CGImage;

    // Get the image size
    width = CGImageGetWidth(cgImage);
    height = CGImageGetHeight(cgImage);

    // Record the number of channels
    channels = CGImageGetBitsPerPixel(cgImage)/CGImageGetBitsPerComponent(cgImage);

    // Generate a CFData object from the CGImage object (a CFData object represents an area of memory)
    CFDataRef imageData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));

    // Copy the image data for use by Open GL
    ret = [self copyImageDataForOpenGL: imageData];
    CFRelease(imageData);
}

return ret;
}


- (void)dealloc
{
if (pngData) {
    delete[] pngData;
}

[super dealloc];
}

@end


@implementation Texture (TexturePrivateMethods)

- (BOOL)copyImageDataForOpenGL:(CFDataRef)imageData
{    
if (pngData) {
    delete[] pngData;
}

pngData = new unsigned char[width * height * channels];
const int rowSize = width * channels;
const unsigned char* pixels = (unsigned char*)CFDataGetBytePtr(imageData);

// Copy the row data from bottom to top
for (int i = 0; i < height; ++i) {
    memcpy(pngData + rowSize * i, pixels + rowSize * (height - 1 - i), width * channels);
}

return YES;
}
That Guy
  • 460
  • 1
  • 7
  • 17

2 Answers2

2

Odds are, you're not seeing the true memory usage of your application. As I explain in this answer, the Allocations instrument hides memory usage from OpenGL ES, so you can't use it to measure the size of your application. Instead, use the Memory Monitor instrument, which I'm betting will show that your application is using far more RAM than you think. This is a common problem people run into when trying to optimize OpenGL ES on iOS using Instruments.

If you're concerned about which objects or resources could be accumulating in memory, you can use the heap shots functionality of the Allocations instrument to identify specific resources that are allocated but never removed when performing repeated tasks within your application. That's how I've tracked down textures and other items that were not being properly deleted.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • Thanks for this. The dealloc method in Textures.mm had to be modified to delete the texture properly. – That Guy Jun 13 '12 at 10:40
1

Seeing some code would help, but I can make some gusses:

I have about 40 different models I need to include in my app, so in the interests of memory conservation I am loading the objects (and rendering textures etc) dynamically in the app as I need them. I tried to copy the UIScrollView lazy loading idea. The three 4mb allocations are the textures I have loaded into memory ready for when the user selects a different model to display. (...)

This kind of approach is not ideal; and it's most likely the reason for your problems, if the memory is not properly deallocated. Eventually you'll run out of memory and then your process dies if you don't take proper precautions. It's very likely that the engine used has some memory leak, exposed by your access scheme.

Today operating systems don't differentiate between RAM and storage. To them it's all just memory and all address space is backed by the block storage system anyway (if there's actually some storage device attached doesn't matter).

So here's what you should do: Instead of read-ing your models into memory, you should memory map them (mmap). This tells the OS "this part of storage should be visible in address space" and the OS kernel will do all the necessary transfers when they're due.

Note that Vurforia's ImageTagets sample app also has Uninitialized Texture Data (about one per frame), so I don't think this is the problem.

This is a strong indicator, that OpenGL texture objects don't get properly deleted.

Any help would be appreciated!!

My advice: Stop programming like it was the 1970ies. Today's computers and operating systems work differently. See also http://www.varnish-cache.org/trac/wiki/ArchitectNotes

datenwolf
  • 159,371
  • 13
  • 185
  • 298