iphone - iPhone OpenGL ES 2.0 中 glReadPixels 的更快替代品

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

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

最佳答案

从 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 视频。

我的实现可以在我的开源中的 GPUImageMovieWriter 类中找到 GPUImage框架,但它的灵感来自 Dennis Muhlestein's article on the subject和 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);
}

这会从与我的 Assets 写入器输入相关联的池中提取一个像素缓冲区,创建一个纹理并将其关联起来,并将该纹理用作我的 FBO 的目标。

渲染一帧后,我锁定像素缓冲区的基址:

CVPixelBufferLockBaseAddress(pixel_buffer, 0);

然后简单地将它输入到我的 Assets 编写器中进行编码:

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 中使用的像素缓冲池看到的要少。

遗憾的是,这些都没有在任何地方记录下来,因为它极大地提高了视频捕获性能。

关于iphone - iPhone OpenGL ES 2.0 中 glReadPixels 的更快替代品,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9550297/

相关文章:

ios - 我可以从现有应用程序创建新的iOS应用程序吗?

iphone - 如何在 UIPickerView 标签旁边获得复选标记?

ios - InAppSettings 在启动时未填充

javascript - WebGL 几何着色器等效?

iphone - 如何使用 OpenGL ES 2.0 旋转对象?

iphone - 将 UITableView 分隔符隐藏在 contentView 后面

iphone - NSUserDefaults 中的 NSMutableArray

ios - 如何在 iOS 中调整 UITabBarController 的大小?

ios - UITableViewHeaderFooterView contentView 未设置为透明

ios - 执行 CMSampleBuffer OpenGL Swift 的平移/旋转