Более быстрая альтернатива glReadPixels в iPhone OpenGL ES 2.0 - PullRequest
66 голосов
/ 04 марта 2012

Есть ли более быстрый способ доступа к буферу кадров, чем использование glReadPixels? Мне понадобится доступ только для чтения к небольшой прямоугольной области рендеринга в буфере кадров, чтобы обрабатывать данные дальше в ЦП. Производительность важна, потому что я должен выполнять эту операцию повторно. Я искал в Интернете и нашел какой-то подход, например использование Pixel Buffer Object и glMapBuffer, но кажется, что OpenGL ES 2.0 их не поддерживает.

Ответы [ 2 ]

131 голосов
/ 14 марта 2012

Начиная с iOS 5.0, теперь есть более быстрый способ получения данных из OpenGL ES.Это не очевидно, но оказывается, что поддержка кэша текстур, добавленная в iOS 5.0, не только работает для быстрой загрузки кадров камеры в OpenGL ES, но и может использоваться в обратном порядке, чтобы получить быстрый доступ к необработанным пикселям.в текстуре OpenGL ES.

Вы можете воспользоваться этим, чтобы получить пиксели для рендеринга ES OpenGL, используя объект кадрового буфера (FBO) с прикрепленной текстурой, причем эта текстура была получена из кэша текстур.,После рендеринга вашей сцены в этот FBO пиксели BGRA для этой сцены будут содержаться в вашем CVPixelBufferRef, поэтому вам не нужно будет опускать их, используя glReadPixels().

Это намного, намного быстрее, чемиспользуя glReadPixels() в моих тестах.Я обнаружил, что на моем iPhone 4 glReadPixels() было узким местом при чтении видеокадров 720p для кодирования на диск.Это ограничивало кодирование от 8 до 9 FPS.Замена этого на быстрое чтение текстурного кэша позволяет мне теперь кодировать видео 720p со скоростью 20 кадров в секунду, и узкое место перешло от считывания пикселей к обработке OpenGL ES и фактическим частям кодирования фильма конвейера.На iPhone 4S это позволяет вам записывать видео 1080p с полными 30 кадрами в секунду.

Моя реализация может быть найдена в классе GPUImageMovieWriter в рамках моего открытого исходного кода GPUImage , но она была вдохновлена Статья Денниса Мюлштейна на эту тему и пример приложения Apple ChromaKey (которое было доступно только на WWDC 2011).

Я начинаю с настройки своего AVAssetWriter, добавления ввода и настройки пикселя.буферный ввод.Следующий код используется для настройки ввода в пиксельном буфере:

NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
                                                       [NSNumber numberWithInt:videoSize.width], kCVPixelBufferWidthKey,
                                                       [NSNumber numberWithInt:videoSize.height], kCVPixelBufferHeightKey,
                                                       nil];

assetWriterPixelBufferInput = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:assetWriterVideoInput sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];

Получив его, я настраиваю FBO, которому я буду рендерить мои видеокадры, используя следующий код:

if ([GPUImageOpenGLESContext supportsFastTextureUpload])
{
    CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)[[GPUImageOpenGLESContext sharedImageProcessingOpenGLESContext] context], NULL, &coreVideoTextureCache);
    if (err) 
    {
        NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d");
    }

    CVPixelBufferPoolCreatePixelBuffer (NULL, [assetWriterPixelBufferInput pixelBufferPool], &renderTarget);

    CVOpenGLESTextureRef renderTexture;
    CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,
                                                  NULL, // texture attributes
                                                  GL_TEXTURE_2D,
                                                  GL_RGBA, // opengl format
                                                  (int)videoSize.width,
                                                  (int)videoSize.height,
                                                  GL_BGRA, // native iOS format
                                                  GL_UNSIGNED_BYTE,
                                                  0,
                                                  &renderTexture);

    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);
}

При этом извлекается пиксельный буфер из пула, связанного с моим входом средства записи ресурсов, создается и связывается с ним текстура, и эта текстура используется в качестве цели для моего FBO.

Как только яотрисовав кадр, я блокирую базовый адрес буфера пикселей:

CVPixelBufferLockBaseAddress(pixel_buffer, 0);

, а затем просто передаю его в мой модуль записи ресурсов для кодирования:

CMTime currentTime = CMTimeMakeWithSeconds([[NSDate date] timeIntervalSinceDate:startTime],120);

if(![assetWriterPixelBufferInput appendPixelBuffer:pixel_buffer withPresentationTime:currentTime]) 
{
    NSLog(@"Problem appending pixel buffer at time: %lld", currentTime.value);
} 
else 
{
//        NSLog(@"Recorded pixel buffer at time: %lld", currentTime.value);
}
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);

if (![GPUImageOpenGLESContext supportsFastTextureUpload])
{
    CVPixelBufferRelease(pixel_buffer);
}

Обратите внимание, что ни в коем случаездесь я читаю что-нибудь вручную.Кроме того, текстуры изначально в формате BGRA, и это то, что AVAssetWriters оптимизирует для использования при кодировании видео, поэтому здесь не нужно ничего менять.Необработанные пиксели BGRA просто подаются в кодировщик для создания фильма.

Помимо использования этого в AVAssetWriter, у меня есть некоторый код в этого ответа , который я использовал дляизвлечение необработанных пикселей.На практике также наблюдается значительное ускорение по сравнению с использованием glReadPixels(), хотя меньше, чем я вижу в пуле буферов пикселей, который я использую с AVAssetWriter.

Обидно, что ничего из этого нигде не задокументировано, потому что это значительно повышает производительность захвата видео.

0 голосов
/ 10 октября 2014

Что касается того, что Атисман упоминал о черном экране, у меня тоже была эта проблема. Убедитесь, что все в порядке с вашей текстурой и другими настройками. Я пытался запечатлеть слой OpenGL в AIR, что я и сделал в итоге, проблема заключалась в том, что когда я не установил значение «deepAndStencil» в значение «true» случайно в манифесте приложения, моя текстура FBO имела половину высоты (экран был разделен пополам и зеркальный, я думаю, из-за материала текстуры обтекания парам). И мое видео было черным.

Это было довольно неприятно, так как, исходя из того, что Брэд публикует, это должно было сработать, как только у меня появились данные в текстуре. К сожалению, это не так, все должно быть «правильно», чтобы это работало - данные в текстуре не являются гарантией для просмотра равных данных в видео. Как только я добавил глубину и AndStencil, моя текстура зафиксировалась на полную высоту, и я начал получать запись видео прямо из слоя AIR OpenGL, без glReadPixels или чего-либо еще :)

Так что да, то, что описывает Брэд, действительно работает, без необходимости воссоздавать буферы в каждом кадре, вам просто нужно убедиться, что ваши настройки правильные. Если вы получаете черноту, попробуйте поиграть с размерами видео / текстуры, возможно, или с некоторыми другими настройками (настройка вашего FBO?).

...