• 2019-03-21

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

    Views: 11912 | No 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

    Posted by ideawu at 2019-03-21 17:07:19
  • 2019-03-13

    2018款MacBook Pro 15寸AMD Radeon Pro Vega 20显卡导致闪屏问题

    Views: 12755 | 3 Comments

    在至少两台这个显卡的2018款 MacBook Pro 15 寸电脑上已经测试发现,苹果用的最高性能的 AMD Radeon Pro Vega 20 显卡可能有驱动问题,或者硬件问题,会导致系统在 1440x900 分辨率下出现闪屏(瞬间花屏)的问题。

    这个闪屏问题很容易复现:

    * 把分辨率从默认的 1680x1050 改成 1440x900
    * 运行 iMovie,或者 Chess 等等使用高性能显卡的应用
    * 打开 App Store,搜索 iMovie 或者别的软件,然后播放软件的介绍视频

    这时你应该能发现屏幕隔几秒就花屏闪一次。如果不出现,你可以试着锁屏,等屏幕黑了之后,再登录系统,确保视频在播放着。

    Posted by ideawu at 2019-03-13 18:06:24
  • 2019-02-28

    macOS NSView 实现 drag and drop 拖放

    Views: 11971 | No Comments

    macOS 实现 drag and drop 拖放很复杂,因为苹果的开发者中心把很多 demo 代码都不知道放到哪里去了,你用苹果开发者网站找不到 Sample Code,用 Xcode 帮助也没法找到(补充:sample code 在 https://developer.apple.com/library/archive/navigation/#section=Resource%20Types&topic=Sample%20Code )。而通过搜索引擎找的话,各种例子不是老旧就是没讲对。

    其实 drag and drop 很简单。有两个概念:DraggingSource 和 DraggingDestination。你可以把拖放理解两个容器之间的交互,你要将某物从A拖到B然后放下。

    最重要的一点,不管实现什么接口什么方法,你必须告诉系统什么时候拖放开始了。我看了很多文章,都没有重点提到这一点。也就是说,不管你实现什么接口都没有用,你必须主动告诉系统拖放已经开始了。所以,在你的代码中明确地告诉系统这一点,一般是鼠标按住的时候。

    当你决定拖放已经开始了,就调用这个方法:

    // NSView 的方法,当你决定 drag-n-drop 可以开始的时候,调用此方法
    - (NSDraggingSession *)beginDraggingSessionWithItems:(NSArray *)items event:(NSEvent *)event source:(id)source;
    

    调用这个方法之后,系统会自动地更新拖动过程的示意图的位置。当然,你要告诉系统示意图的开始位置和图片,通过上面的方法中的参数。另外,drag-n-drop 通过剪贴板在源和目的之间进行通信,所以剪贴板充当通信信道。下面的代码配置了通信信道和示意图:

    // NSPasteboardItem 用于在 drag-n-drap 的双方之间进行通信
    NSPasteboardItem *pbItem = [NSPasteboardItem new];
    // 你可以直接传输数据, type 可自定义.
    [pbItem setString:@"Transition" forType:NSPasteboardTypeString];
    // 或者指定传输的数据由 NSPasteboardItemDataProvider 提供。
    //[pbItem setDataProvider:self forTypes:@[NSPasteboardTypeString]];
    // NSDraggingItem 用于显示 drag-n-drop 过程的示意图
    NSDraggingItem *dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
    NSImage *img = [NSImage imageNamed:NSImageNameHomeTemplate];
    NSRect frame = NSMakeRect(0, 0, img.size.width, img.size.height);
    // draggingFrame 用于指定示意图的初始位置(在当前 NSView 中),contents 是示意图(NSImage)
    [dragItem setDraggingFrame:frame contents:img];
    

    这认为用网络通信模型来理解会更好,网络和通信是一个广义的概念,可以解释很多东西,我之前有文章介绍过:http://www.ideawu.net/blog/archives/528.html

    弄明白了上面的之后,你再查看这几个接口就懂了:NSDraggingSource, NSDraggingDestination, NSPasteboardItemDataProvider

    Posted by ideawu at 2019-02-28 19:52:20
  • 2019-02-20

    Xcode 静态链接库找不到的问题

    Views: 12209 | No Comments

    如果你的 Xcode 程序编译正常,但执行时遇到类似的报错:

    +[NSCharacterSet SVGWhitespaceCharacterSet]: unrecognized selector sent to class
    

    这种问题是因为静态链接库中的 Objective-C 的 Category 符号没有被链接进你的程序。你可以用下面的命令看看符号有没有被链接:

    nm Cli | grep SVGWhitespaceCharacterSet
    

    网上说你可以在 Other Linker Flags 里增加 -ObjC,但如果你的项目还要用别的非 OC 的库,则会报错,例如你同时使用 C++ 库。正确的做法是,在 Other Linker Flags 里增加

    -force_load $(PROJECT_DIR)/...../libxxx.a
    

    -force_load 后面跟绝对路径。

    Posted by ideawu at 2019-02-20 19:44:17
  • 2019-02-20

    Xcode项目添加libxml

    Views: 16342 | No Comments

    把 /usr/include/libxml2 添加到 header search path

    Posted by ideawu at 18:04:09
  • 2019-02-19

    Mac defaults 存储路径

    Views: 11738 | No Comments

    non-sandboxed:

    ~/Library/Preferences/com.example.myapp.plist

    sandboxed:

    ~/Library/Containers/com.example.myapp/Data/Library/Preferences/com.example.myapp.plist

    defaults 命令只能读取 sandboxed 的。

    但是,Xcode 调试时运行的 app,defaults 存在哪个目录呢?我也不知道。

    Posted by ideawu at 2019-02-19 19:11:33
|<<<1234567>>>| 1/7 Pages, 37 Results.