Реальное использование памяти увеличивается в 2 раза, когда я загружаю текстуру в OpenGLES - PullRequest
1 голос
/ 10 ноября 2011

Я бился головой об это некоторое время. Ситуация такова: я создал 2D-игру для iphone на своем собственном небольшом движке OpenGL 2D-игры (который я построил в качестве эксперимента, но который теперь может действительно поставляться). Я пытаюсь взять память под контроль. Я использую текстурные атласы, и я знаком с PVRTC (но на данный момент я не использую его). Проблема заключается в том, что если я загружу текстурный атлас 1024x1024 png, который, как ожидается, займет около 4 мегабайт при развертывании в памяти (1024 x 1024 x 4 байт на пиксель - RBGA8888 = 4 мегабайта), то реальное использование памяти (согласно Instruments- Память монитора) увеличивается на 8 мегабайт. Aaagh!

Я знаю, что OpenGLES берет данные текстуры, расширяет их в память, затем переупорядочивает пиксели для работы на чипе PowerVR, а затем создает из них текстуру (или что-то подобное). Возможно ли, что эта память не освобождается? Так что у меня есть две копии каждой текстуры, сидящие в памяти? Со стороны ObjectiveC я вижу, что все правильно высвобождается. Но что происходит за OpenGL API, я не знаю. Я, наверное, что-то упустил.

Моя реализация для загрузки текстур, которую я получил из книги О'Рейли по разработке игр для iPhone. Вот ключевые моменты, которые я использую для реализации:

Шаг 1 - получить данные изображения правильного размера (степень 2):

- (id) initWithImage:(UIImage *)uiImage
{
    NSUInteger            width, height, i;
    CGContextRef          context = nil;
    void*                 data = nil;
    CGColorSpaceRef       colorSpace;
    void*                 tempData;
    unsigned int*         inPixel32;
    unsigned short*       outPixel16;
    BOOL                  hasAlpha;
    CGImageAlphaInfo      info;
    CGAffineTransform     transform;
    CGSize                imageSize;
    GLTexturePixelFormat  pixelFormat;
    CGImageRef            image;
    UIImageOrientation    orientation;
    BOOL                  sizeToFit = NO;

    image = [uiImage CGImage];
    orientation = [uiImage imageOrientation]; 

    if(image == NULL) {
        [self release];
        NSLog(@"Image is Null");
        return nil;
    }


    info = CGImageGetAlphaInfo(image);
    hasAlpha = ((info == kCGImageAlphaPremultipliedLast) || (info == kCGImageAlphaPremultipliedFirst) || (info == kCGImageAlphaLast) || (info == kCGImageAlphaFirst) ? YES : NO);
    if(CGImageGetColorSpace(image)) {
        if(hasAlpha)
            pixelFormat = kGLTexturePixelFormat_RGBA8888;
        else
            pixelFormat = kGLTexturePixelFormat_RGB565;
    } else { //NOTE: No colorspace means a mask image
        pixelFormat = kGLTexturePixelFormat_A8;
    }

    imageSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
    transform = CGAffineTransformIdentity;

    width = imageSize.width;
    if((width != 1) && (width & (width - 1))) {
        i = 1;
        while((sizeToFit ? 2 * i : i) < width)
        i *= 2;
        width = i;
    }

    height = imageSize.height;
    if((height != 1) && (height & (height - 1))) {
        i = 1;
        while((sizeToFit ? 2 * i : i) < height)
        i *= 2;
        height = i;
    }

    while((width > kMaxTextureSize) || (height > kMaxTextureSize)) {
        width /= 2;
        height /= 2;
        transform = CGAffineTransformScale(transform, 0.5, 0.5);
        imageSize.width *= 0.5;
        imageSize.height *= 0.5;
    }

    switch(pixelFormat) {       
        case kGLTexturePixelFormat_RGBA8888:
            colorSpace = CGColorSpaceCreateDeviceRGB();
            data = malloc(height * width * 4);
            context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
            CGColorSpaceRelease(colorSpace);
            break;
        case kGLTexturePixelFormat_RGB565:
            colorSpace = CGColorSpaceCreateDeviceRGB();
            data = malloc(height * width * 4);
            context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big);
            CGColorSpaceRelease(colorSpace);
            break;

        case kGLTexturePixelFormat_A8:
            data = malloc(height * width);
            context = CGBitmapContextCreate(data, width, height, 8, width, NULL, kCGImageAlphaOnly);
            break;              
        default:
            [NSException raise:NSInternalInconsistencyException format:@"Invalid pixel format"];
    }


    CGContextClearRect(context, CGRectMake(0, 0, width, height));
    CGContextTranslateCTM(context, 0, height - imageSize.height);

    if(!CGAffineTransformIsIdentity(transform))
        CGContextConcatCTM(context, transform);
    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);

    //Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGGBBBBB"
    if(pixelFormat == kGLTexturePixelFormat_RGB565) {
        tempData = malloc(height * width * 2);
        inPixel32 = (unsigned int*)data;
        outPixel16 = (unsigned short*)tempData;
        for(i = 0; i < width * height; ++i, ++inPixel32)
            *outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | ((((*inPixel32 >> 8) & 0xFF) >> 2) << 5) | ((((*inPixel32 >> 16) & 0xFF) >> 3) << 0);
        free(data);
        data = tempData;

    }

    self = [self initWithData:data pixelFormat:pixelFormat pixelsWide:width pixelsHigh:height contentSize:imageSize];

    CGContextRelease(context);
    free(data); 

    return self;
}

Шаг 2 - привязка и загрузка текстуры:

- (id) initWithData:(const void*)data pixelFormat:(GLTexturePixelFormat)pixelFormat pixelsWide:(NSUInteger)width pixelsHigh:(NSUInteger)height contentSize:(CGSize)size
{
    GLint saveName;
    if((self = [super init])) {
        glGenTextures(1, &_name); //get a new texture id.  _name increases as more textures are loaded
        glGetIntegerv(GL_TEXTURE_BINDING_2D, &saveName); //generally, saveName==1.  gets existing bound texture, so we can restore it after load.
        glBindTexture(GL_TEXTURE_2D, _name); //start working with our new texture id
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  //added by ijames

       //associate pixel data with the texture id.
        switch(pixelFormat) {
            case kGLTexturePixelFormat_RGBA8888:
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
                break;
            case kGLTexturePixelFormat_RGB565:
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data);
                break;
            case kGLTexturePixelFormat_A8:
                glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
                break;
            default:
                [NSException raise:NSInternalInconsistencyException format:@""];

        }

        glBindTexture(GL_TEXTURE_2D, saveName); //restore the previous texture binding.

        //NSLog(@"name %d, savename %d", _name, saveName);

        _size = size;
        _width = width;
        _height = height;
        _format = pixelFormat;
        _maxS = size.width / (float)width;
        _maxT = size.height / (float)height;
    }

    return self;
}

Вы видите что-то ужасно не так? Кто-нибудь из вас сталкивался с этой проблемой раньше? Откуда же это призрачное воспоминание?

Спасибо за ваше время и мысли!

РЕДАКТИРОВАТЬ 1:

Я просто добавил несколько строк в initWithImage: непосредственно перед вызовом initWithData:, чтобы конвертировать любые текстуры RGBA8888 в RGBA4444 на лету при загрузке, просто чтобы посмотреть, что произойдет и насколько плохо будет попадать графика. В результате реальное использование памяти сократилось почти в 2 раза. Это означает, что где бы ни происходило удвоение тайны, оно происходит на этапе или после шага initWithData:. Еще раз спасибо за ваши мысли!

РЕДАКТИРОВАТЬ 2:

Чтобы ответить на один из комментариев - вот как называется initWithImage: (это единственное место, где это происходит - из класса ResourceManager, который управляет кэшем для текстур):

//NOTE: 'texture' and '_textures' are declared earlier...

//if the texture doesn't already exist, create it and add it to the cache
NSString * fullPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: fileName];

UIImage * textureImg = [[UIImage alloc] initWithContentsOfFile: fullPath];
texture = [[GLTexture alloc] initWithImage: textureImg];  //here's the call
[textureImg release];

[_textures setValue: texture forKey: fileName];
return [texture autorelease];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...