Самый эффективный способ glTexture из NSImage?

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

Приложение OpenGL Cocoa/OSX Desktop для версии 10.8+

У меня есть ситуация, когда я получаю объект NSImage один раз за кадр и хотел бы преобразовать его в текстуру openGL (это видеокадр с IP-камеры). Из 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% загрузки метода (но, может быть, это нормально.)

===========


person andrew    schedule 31.03.2014    source источник
comment
Вы профилировали это? Вы уверены, что glTexImage2D() действительно самая медленная часть из вышеперечисленных? Размещение этого в NSImage и перерисовка через Core Graphics, казалось бы, намного медленнее, чем реальная загрузка текстуры.   -  person Brad Larson    schedule 31.03.2014
comment
Только что выложил скриншот профиля времени. Возможно, я неправильно читаю, но мне кажется, что два узких места выделяют NSBitmapImageRep и glTexImage2D.   -  person andrew    schedule 31.03.2014
comment
Также кажется, что загрузка текстуры является худшим преступником ... что, я согласен, удивительно.   -  person andrew    schedule 31.03.2014
comment
Я все еще думаю, что вы захотите найти способ избежать прохождения через NSImage, возможно, используя другую реализацию декодирования JPEG, которая получает необработанные байты. У меня недостаточно информации для полного ответа, но ознакомьтесь с рекомендациями Apple по загрузке текстур: developer.apple.com/library/mac/documentation/graphicsimaging/ , в частности параметр GL_TEXTURE_STORAGE_HINT_APPLE и функцию glTextureRangeAPPLE(). Я использовал их в прошлом, чтобы значительно ускорить загрузку текстур.   -  person Brad Larson    schedule 31.03.2014
comment
Да ... Спасибо за ваше время и за конкретные указатели на функции Apple, которые я не видел. Я думаю, что прихожу к тому же несколько разочаровывающему выводу (что мне просто нужно получать текстуры по-другому). Я собираюсь оставить вопрос открытым немного дольше, если есть какие-либо другие идеи. Если нет, то я все равно попрошу вас опубликовать это как ответ и примете? Спасибо!   -  person andrew    schedule 31.03.2014


Ответы (1)


В зависимости от того, как поступают ваши данные, вы можете использовать объект Pixel Buffer Object (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 — в противном случае вам все равно придется выполнять memcpy для PBO, теряя любую выгоду, которую это может принести.

person Michael    schedule 01.04.2014
comment
Спасибо за это, особенно за объяснение, почему PBO быстрее. Я собираюсь принять этот ответ со следующим примечанием для себя или других людей, обнаруживших это: Ответ на мой вопрос (наиболее эффективный способ из NSImage->glTexture): Не делайте этого: D Приведенный выше код примерно такой как бы то ни было, если вам нужны данные с разумной частотой кадров, найдите другой способ доступа к пикселям — переход через NSImage слишком медленный. - person andrew; 03.04.2014