2019-03-21

CVPixelBufferRef 与 CVOpenGLTextureRef: 图像处理中内存与显存的交互

Views: 31174 | Add Comments

现代计算机系统在进行图像处理时,可以利用 CPU 或者显卡两种芯片之一进行处理,也可以同时使用两者。本文讨论在 macOS 上的图像处理。

1. 完全使用 CPU 进行图像处理

图像(如 png)从硬盘中读取并解压到内存中,之后的图像处理完全用 CPU 操作内存,和显卡无关。

2. 完全使用显卡进行图像处理

图像(如 png)从硬盘中读取并解压到内存中,然后传到显卡显存中(创建 OpenGL texture),图像一旦传到显卡,内存中的图像数据就可以删除了。

3. 同时使用 CPU 和显卡进行图像处理

图像同时存于内存以及显存中,有时利用显卡进行处理,有时利用 CPU 进行处理。因为图像数据同时保存于内存和显存中,所以需要某种绑定机制,关联内存中的图像和显存中的图像,并在一方变更时,更新另一方。

# CVPixelBufferRef 与 CVOpenGLTextureRef

CVPixelBufferRef 表示内存中的图像。

CVOpenGLTextureRef 表示显存中的图像,内部使用 OpenGL 的 texture。

苹果的 Core Video 框架提供了显存图像到内存图像的单向实时绑定机制,也即绑定 CVPixelBufferRef 和 CVOpenGLTextureRef,当后者(显存)更新时,框架自动更新前者(内存)。注意,这种实时绑定是单向的,更新的传导只有一个方向,即从显存到内存。

使用 CVOpenGLTextureCacheCreateTextureFromImage() 函数来建立这种绑定关系。绑定建立后,用户(也即你的代码)对显存或者内存的操作,都必须涉及到锁,因为框架本身同时也会操作这两份数据。

涉及到多者同时操作一份数据的情况,都需要锁。

使用 CVPixelBufferLockBaseAddress() 和 CVPixelBufferUnlockBaseAddress() 两个函数进行锁操作。显然,当你的代码要操作内存数据时,你应该 lock CVPixelBufferRef,当你想操作显存数据时,就应该 unlock CVPixelBufferRef。

前面提到,这种单向绑定会将显存的更新传导到内存,所以,当你更新完显存(即执行 OpenGL glFinish 操作,文档提到是 glFlush 之后)之后,你就获得了 OpenGL 渲染的图像数据(OpenGL 截图),其内部实现应该是用 glReadPixels() 或者 glGetTexImage() 函数。这时,你就可以把 OpenGL 渲染的结果保存成 png 文件了。

# 关于 Metal 框架的 CVMetalTextureRef

CVMetalTextureRef 是用来替代 CVPixelBufferRef 的,因为苹果已经发布了 Metal 框架用来替代所有苹果操作系统上的 OpenGL。

提一句,从面向对象的角度 CVPixelBufferRef CVOpenGLTextureRef CVMetalTextureRef 这三者都是 CVImageBufferRef 的子类。不过,在 C 语言里,这三者是同一个结构体,只不过用 typedef 命了 4 个名字而已。

typedef CVImageBufferRef CVPixelBufferRef;
typedef CVImageBufferRef CVOpenGLTextureRef;
typedef CVImageBufferRef CVMetalTextureRef;

CVImageBufferRef(也即 CVBufferRef)内部应该是用了 type 字段来表示不同的子类,毕竟 C 语言没有 C++ 那样的类和继承,只能用结构体来实现子类,都是惯用法,大同小异。

如果你使用 Core Graphics, 那么从内存到显存的路径是这样的: CGImage => CGBitmapContext => OpenGL texture

Related posts:

  1. OpenGL frame buffer object无法glReadPixels
  2. 一个 GUI 系统的组成部分
  3. Redis 的作者狂喷某 NoSQL 数据库
  4. C++ const& 的坑
  5. Zend Framework 的缓存模块 Zend_Cache 使用
Posted by ideawu at 2019-03-21 17:07:19

Leave a Comment