Самый эффективный способ получить текстуру от NSImage?
(ОБНОВЛЕНИЕ НИЖЕ...)
OpenGL Cocoa / OSX Desktop ap для таргетинга 10.8+
У меня есть ситуация, когда я получаю объект NSImage один раз за кадр и хотел бы преобразовать его в текстуру openGL (это видеокадр из IPCamera). Из SO и Интернета самый полезный метод утилит, который я смог найти для преобразования NSImage в glTexture, - это код ниже.
Хотя это подходит для случайных ситуаций (загрузка IE), это огромный скачок производительности и ужасно запускаться один раз за кадр. Я профилировал код и сузил узкое место до двух вызовов. Вместе эти звонки составляют почти две трети времени работы всего моего приложения.
Создание битмапреп
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithData:[inputImage TIFFRepresentation]];
Думая, что проблема, возможно, заключалась в том, как я получал растровое изображение, и, поскольку я слышал о плохих вещах с точки зрения производительности в TIFFRepresentation, я попробовал это вместо этого, что действительно быстрее, но только немного, и также ввел некоторую странную смену цвета (все выглядит красным)
NSRect rect = NSMakeRect(0, 0, inputImage.size.width, inputImage.size.height); CGImageRef cgImage = [inputImage CGImageForProposedRect:&rect context:[NSGraphicsContext currentContext] hints:nil]; NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
Вызов glTexImage2D (я не знаю, как это улучшить.)
Как я могу сделать метод ниже более эффективным?
В качестве альтернативы, скажите мне, что я делаю это неправильно, и я должен искать в другом месте? Кадры поступают как MJPEG, поэтому я мог бы потенциально использовать NSData до того, как он конвертируется в NSImage. Тем не менее, это в формате JPEG, так что мне придется иметь дело с этим. Я также на самом деле хочу объект NSImage для другой части приложения.
-(void)loadNSImage:(NSImage*)inputImage intoTexture:(GLuint)glTexture{
// If we are passed an empty image, just quit
if (inputImage == nil){
//NSLog(@"LOADTEXTUREFROMNSIMAGE: Error: you called me with an empty image!");
return;
}
// We need to save and restore the pixel state
[self GLpushPixelState];
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, glTexture);
// Aquire and flip the data
NSSize imageSize = inputImage.size;
if (![inputImage isFlipped]) {
NSImage *drawImage = [[NSImage alloc] initWithSize:imageSize];
NSAffineTransform *transform = [NSAffineTransform transform];
[drawImage lockFocus];
[transform translateXBy:0 yBy:imageSize.height];
[transform scaleXBy:1 yBy:-1];
[transform concat];
[inputImage drawAtPoint:NSZeroPoint
fromRect:(NSRect){NSZeroPoint, imageSize}
operation:NSCompositeCopy
fraction:1];
[drawImage unlockFocus];
inputImage = drawImage;
}
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithData:[inputImage TIFFRepresentation]];
// Now make a texture out of the bitmap data
// Set proper unpacking row length for bitmap.
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)[bitmap pixelsWide]);
// Set byte aligned unpacking (needed for 3 byte per pixel bitmaps).
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
NSInteger samplesPerPixel = [bitmap samplesPerPixel];
// Nonplanar, RGB 24 bit bitmap, or RGBA 32 bit bitmap.
if(![bitmap isPlanar] && (samplesPerPixel == 3 || samplesPerPixel == 4)) {
// Create one OpenGL texture
// FIXME: Very slow
glTexImage2D(GL_TEXTURE_2D, 0,
GL_RGBA,//samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8,
(GLint)[bitmap pixelsWide],
(GLint)[bitmap pixelsHigh],
0,
GL_RGBA,//samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
GL_UNSIGNED_BYTE,
[bitmap bitmapData]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
}else{
[[NSException exceptionWithName:@"ImageFormat" reason:@"Unsupported image format" userInfo:nil] raise];
}
[self GLpopPixelState];
}
Скриншот профиля:
Обновить
Основываясь на комментариях Брэда, я решил просто обойти NSImage. В моем конкретном случае я смог получить доступ к данным JPG как объекту NSData до того, как он был преобразован в NSImage, и это прекрасно работало:
[NSBitmapImageRep imageRepWithData: imgData];
Используя более или менее тот же метод, описанный выше, но начиная непосредственно с растровым представлением, загрузка ЦП снизилась с 80% до 20%, и я доволен скоростью. У меня есть решение для моего ап.
Я все еще хотел бы знать, есть ли ответ на мой первоначальный вопрос или лучше просто принять это как наглядный урок того, чего следует избегать. Наконец, я все еще задаюсь вопросом, возможно ли улучшить время загрузки при вызове glTexImage2D - хотя теперь оно вполне в пределах разумного, оно по-прежнему профилируется как принятие 99% загрузки метода (но, возможно, это нормально).
===========
1 ответ
В зависимости от того, как поступают ваши данные, вы можете использовать объект Pixel Buffer (PBO), который будет использовать DMA при загрузке текстуры (без использования ЦП), тогда как случайный указатель будет скопирован с использованием memcpy (с использованием ЦП).
Apple, описать, как это сделать, здесь: https://developer.apple.com/library/mac/documentation/graphicsimaging/conceptual/opengl-macprogguide/opengl_texturedata/opengl_texturedata.html
По сути, PBO дает вам кусок памяти, который OpenGL может DMA, поэтому используйте его для выделения памяти, в которую вы копируете распакованные кадры, затем рисуйте, связывая буфер и вызывая glTexSubImage2D с nil в качестве ссылки на память.
Это зависит от того, сможете ли вы передавать свои входящие данные прямо в PBO - в противном случае вам все равно придется записываться в PBO, теряя все преимущества, которые он может принести.