【问题标题】:Faster alternative to glReadPixels in iPhone OpenGL ES 2.0iPhone OpenGL ES 2.0 中 glReadPixels 的更快替代品
【发布时间】:2012-03-21 23:02:03
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    关于 atisman 提到的黑屏,我也遇到了这个问题。务必确保您的纹理和其他设置一切正常。我试图捕获 AIR 的 OpenGL 层,我最终做到了,问题是当我没有在应用程序清单中意外地将“depthAndStencil”设置为 true 时,我的 FBO 纹理的高度只有一半(屏幕被分割了一半和镜像,我猜是因为包裹纹理参数的东西)。我的视频是黑色的。

    这非常令人沮丧,因为根据 Brad 发布的内容,一旦我获得了一些纹理数据,它应该就可以工作了。不幸的是,事实并非如此,一切都必须“正确”才能正常工作 - 纹理中的数据并不能保证在视频中看到相同的数据。添加 depthAndStencil 后,我的纹理将自身固定到全高,然后我开始直接从 AIR 的 OpenGL 层获取视频录制,没有 glReadPixels 或任何东西:)

    所以是的,Brad 描述的内容确实有效,无需在每一帧上重新创建缓冲区,您只需要确保您的设置正确。如果您变黑,请尝试使用视频/纹理大小或其他一些设置(您的 FBO 的设置?)。

    【讨论】:

      【解决方案2】:

      从 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 上提供)。

      我首先配置我的 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 使用的像素缓冲池要少。

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

      【讨论】:

      • @atisman - 是的。在我的框架代码中,使用了pixel_buffer,因为我根据您是在 iOS 5.0 还是 4.0 上运行它来进行分支。在 4.0 上,我从池中拉出一个新的像素缓冲区,将其存储在 pixel_buffer 中,并使用 glReadPixels() 拉取数据。在 5.0 上,它只是从现有的缓存像素缓冲区中分配的。我不确定你的情况出了什么问题,但是在你的 FBO 中渲染到纹理之后,获取像素缓冲区中字节的基地址应该指向纹理的字节。
      • @atisman - 经过一些实验,我现在已经在几种不同的情况下对此进行了测试,发现它在所有情况下都具有性能优势。我在这里展示了一个抓取原始像素的示例(用于 CPU 处理和图像保存):stackoverflow.com/a/10455622/19679。您必须确保使用kCVPixelBufferIOSurfacePropertiesKey 才能在此处正常访问直接数据访问。在我测试过的每台设备上,这都比glReadPixels() 快得多。但是,正如您所指出的,模拟器目前不支持这些类型的读取。
      • 感谢您的更新。我又试了一次,但如果不每帧调用CVOpenGLESTextureCacheCreateTextureFromImage 仍然无法使其工作。根据您的帖子,该方法的调用是在设置中完成的,但我不能让它这样工作......
      • 请注意,如果您不使用 AVAssetWriterInputPixelBufferAdaptor 池创建像素缓冲区,则需要将 CVPixelBufferRef 配置为 IOSurface 才能使此技术起作用。有关如何完成此操作的示例,请参阅 Dennis Muhlestein 的文章。
      • @MarkIngram - 是的,glFinish() 是必要的(glFlush() 在 iOS 上的许多情况下不会阻塞)在上面的代码中 -appendPixelBuffer:withPresentationTime: 之前。否则,您会在录制视频时看到屏幕撕裂。我在上面的代码中没有它,但是在我使用上面代码的框架中,它在此之前作为渲染例程的一部分被调用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-28
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      相关资源
      最近更新 更多