内存警告后在 iOS 上运行 OpenGL 崩溃

Posted

技术标签:

【中文标题】内存警告后在 iOS 上运行 OpenGL 崩溃【英文标题】:Crash running OpenGL on iOS after memory warning 【发布时间】:2012-06-12 06:13:38 【问题描述】:

我遇到了一个带有 OpenGL 组件的应用程序在 iPad 上崩溃的问题。该应用程序会引发内存警告并崩溃,但它似乎并没有使用那么多内存。我错过了什么吗?

该应用基于 Vuforia 增强现实系统(大量借鉴 ImageTargets 示例)。我需要在我的应用程序中包含大约 40 个不同的模型,因此为了节省内存,我会根据需要在应用程序中动态加载对象(和渲染纹理等)。我试图复制 UIScrollView 延迟加载的想法。这三个 4mb 分配是我已经加载到内存中的纹理,供用户选择不同的模型进行显示时使用。

这里有什么奇怪的吗?

我对 OpenGL 知之甚少(我选择 Vurforia 引擎的部分原因)。下面这个屏幕截图中的任何内容都应该与我有关吗?请注意,Vurforia 的 ImageTagets 示例应用程序也有未初始化的纹理数据(每帧大约一个),所以我认为这不是问题。

任何帮助将不胜感激!

这是生成 3D 对象的代码(在 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;

这是纹理对象中的代码。

- (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;

【问题讨论】:

【参考方案1】:

很可能,您没有看到应用程序的真实内存使用情况。正如我在this answer 中解释的那样,Allocations 工具对 OpenGL ES 隐藏了内存使用情况,因此您不能使用它来测量应用程序的大小。相反,请使用 Memory Monitor 工具,我打赌它会显示您的应用程序使用的 RAM 比您想象的要多得多。这是人们在尝试使用 Instruments 在 ios 上优化 OpenGL ES 时遇到的常见问题。

如果您担心哪些对象或资源可能会在内存中累积,您可以使用 Allocations 工具的堆快照功能来识别在应用程序中执行重复任务时已分配但从未移除的特定资源。这就是我追踪未正确删除的纹理和其他项目的方式。

【讨论】:

谢谢。必须修改 Textures.mm 中的 dealloc 方法才能正确删除纹理。【参考方案2】:

看一些代码会有所帮助,但我可以做一些猜测:

我的应用程序中需要包含大约 40 个不同的模型,因此为了节省内存,我会根据需要在应用程序中动态加载对象(以及渲染纹理等)。我试图复制 UIScrollView 延迟加载的想法。三个 4mb 的分配是我已经加载到内存中的纹理,当用户选择不同的模型来显示时。 (...)

这种做法并不理想;如果内存未正确释放,这很可能是您出现问题的原因。最终,如果您不采取适当的预防措施,您的内存将耗尽,然后您的进程就会终止。很可能使用的引擎有一些内存泄漏,由您的访问方案暴露。

今天的操作系统不区分 RAM 和存储。对他们来说,这只是内存,所有地址空间都由块存储系统支持(如果实际上连接了一些存储设备并不重要)。

所以你应该这样做:而不是read-将你的模型放入内存中,你应该对它们进行内存映射(mmap)。这告诉操作系统“这部分存储应该在地址空间中可见”,并且操作系统内核将在到期时执行所有必要的传输。

请注意,Vurforia 的 ImageTagets 示例应用程序也有未初始化的纹理数据(大约每帧一个),所以我认为这不是问题。

这是一个强有力的指标,表明 OpenGL 纹理对象没有被正确删除。

任何帮助将不胜感激!

我的建议:停止像 1970 年代那样编程。今天的计算机和操作系统的工作方式不同。另见http://www.varnish-cache.org/trac/wiki/ArchitectNotes

【讨论】:

感谢您的意见。值得深思!

以上是关于内存警告后在 iOS 上运行 OpenGL 崩溃的主要内容,如果未能解决你的问题,请参考以下文章

在 UITesting 期间导致内存警告和崩溃的背景图像

iOS4 调用 ImageNamed:仍然泄漏或导致内存问题?

应用程序收到内存不足警告但只有 5.7MB 的活动字节

如何调试由于内存压力导致的 iOS 崩溃

iPhone 相机应用程序在 iPhone 中运行良好,但在 iPod 中崩溃

在 UICollectionView iOS 7 中收到内存警告