Я бился головой об это некоторое время. Ситуация такова: я создал 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];