更快速地替代iPhone OpenGL ES 2.0中的glReadPixels

时间:2012-03-03 22:07:24

标签: iphone ios opengl-es opengl-es-2.0

有没有比使用glReadPixels更快的方式来访问帧缓冲区?我需要对帧缓冲区中的小矩形渲染区域进行只读访问,以便在CPU中进一步处理数据。性能很重要因为我必须重复执行此操作。我在网上搜索并发现了一些方法,比如使用像素缓冲区对象和glMapBuffer,但似乎OpenGL ES 2.0不支持它们。

2 个答案:

答案 0 :(得分:131)

从iOS 5.0开始,现在有更快的方法从OpenGL ES中获取数据。它不是很明显,但事实证明iOS 5.0中添加的纹理缓存支持不仅适用于将相机帧快速上传到OpenGL ES,而且可以反过来使用它来快速访问原始像素在OpenGL ES纹理中。

您可以利用此功能通过使用带有附加纹理的帧缓冲对象(FBO)来获取OpenGL ES渲染的像素,该纹理已从纹理缓存中提供。将场景渲染到FBO后,该场景的BGRA像素将包含在CVPixelBufferRef中,因此无需使用glReadPixels()将其拉下来。

这比在基准测试中使用glReadPixels()快得多。我发现在我的iPhone 4上,glReadPixels()是读取720p视频帧以进行磁盘编码的瓶颈。它将编码限制在超过8-9 FPS的范围内。用快速纹理高速缓存读取代替它允许我现在以20 FPS编码720p视频,并且瓶颈已经从像素读取转移到OpenGL ES处理和管道的实际电影编码部分。在iPhone 4S上,这允许您以30 FPS的速度写入1080p视频。

我的实现可以在我的开源GPUImage框架内的GPUImageMovieWriter类中找到,但它的灵感来自Dennis Muhlestein's article on the subject和Apple的ChromaKey示例应用程序(仅在WWDC 2011上提供)。 / p>

首先配置我的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中使用它之外,我在this answer中有一些我用于原始像素提取的代码。与使用glReadPixels()相比,它在实践中也经历了显着的加速,尽管比我在AVAssetWriter中使用的像素缓冲池要少。

遗憾的是,这一切都没有记录在任何地方,因为它为视频捕获性能提供了巨大的推动力。

答案 1 :(得分:0)

关于atisman提到的关于黑屏的内容,我也有这个问题。确实用你的纹理和其他设置确保一切都很好。我试图捕获AIR的OpenGL层,最后我做了,问题是当我没有设置" depthAndStencil"在应用程序清单中偶然出现,我的FBO纹理高度为一半(屏幕被分成两半并镜像,我猜是因为包裹纹理参数的东西)。我的视频是黑色的。

这是非常令人沮丧的,因为基于Brad发布的内容,一旦我在纹理中有一些数据,它应该只是工作。不幸的是,情况并非如此,一切都必须是'#34;对"它的工作原理 - 纹理中的数据不能保证在视频中看到相同的数据。一旦我添加了depthAndStencil,我的纹理将自己固定到全高,我开始直接从AIR的OpenGL图层录制视频,没有glReadPixels或任何东西:)

所以是的,布拉德描述的确实能够在不需要在每一帧上重新创建缓冲区的情况下工作,您只需要确保您的设置正确。如果您出现黑暗,请尝试使用视频/纹理大小或其他一些设置(设置您的FBO?)。