Самый эффективный способ получить текстуру от NSImage?

(ОБНОВЛЕНИЕ НИЖЕ...)

OpenGL Cocoa / OSX Desktop ap для таргетинга 10.8+

У меня есть ситуация, когда я получаю объект NSImage один раз за кадр и хотел бы преобразовать его в текстуру openGL (это видеокадр из IPCamera). Из SO и Интернета самый полезный метод утилит, который я смог найти для преобразования NSImage в glTexture, - это код ниже.

Хотя это подходит для случайных ситуаций (загрузка IE), это огромный скачок производительности и ужасно запускаться один раз за кадр. Я профилировал код и сузил узкое место до двух вызовов. Вместе эти звонки составляют почти две трети времени работы всего моего приложения.

  1. Создание битмапреп

     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];
    
  2. Вызов 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, теряя все преимущества, которые он может принести.

Другие вопросы по тегам