macOS、iOS的Metal 2开发爬坑记录:摄像头、Capture GPU Frame、Shader调试与GPUImage存在的问题

本文档记录Metal 2配合Xcode 9在macOS High Serria、iOS 8+开发过程遇到的摄像头、Capture GPU Frame与Shader编译调试问题及解决办法。另外,修正了GPUImage源码中对Mac摄像头不支持yuv输出的“不恰当”地说法(至少在macOS High Serria是不恰当的)。

1. 调用iMac摄像头

1.1 摄像头的position属性为AVCaptureDevicePositionUnspecified

在iOS开发中,一般通过AVCaptureDevicePosition(Front或Back)确认访问前后置摄像头。虽然iMac有前置摄像头,然而,它的position属性为nil。因此,当macOS和iOS共享一份代码,遍历[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]返回的AVCaptureDevice列表时,AVCaptureDevice的position属性并不等于AVCaptureDevicePositionFront,而是AVCaptureDevicePositionUnspecified

NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices)
{
    if ([device position] == cameraPosition)
    {
        _inputCamera = device;
    }
}
if (!_inputCamera)
{
    return nil;
}

或者,如果做平台条件编译,直接用默认摄像头即可,示例代码如下所示。

#if TARGET_OS_MAC
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
#endif

正常情况下,iMac 5k摄像头的输出信息如下所示。

<AVCaptureDALDevice: 0x10071e6f0 [FaceTime HD Camera (Built-in)][0x1440000005ac8511]>

1.2 输出yuv像素格式并兼容Metal 2

因为AVFoundation和Metal 2支持macOS和iOS,所以我将音视频输入部分写成同一份源码并加上适当的条件编译。出于性能考虑,iOS一般让摄像头输出yuv像素数据,并且在yuv空间上做些图像处理操作,这就得测试yuv转rgb的shader是否正常工作的。因此,令macOS输出yuv格式数据在此场合是合理的。阅读GPUImage源码,可发现如下注释:

// Despite returning a longer list of supported pixel formats, only RGB, RGBA, BGRA, and the YUV 4:2:2 variants seem to return cleanly

打印AVCaptureVideoDataOutput.availableVideoCVPixelFormatTypes属性,发现其支持yuv420sp、yuv422sp和RGBA及BGRA。

因此,GPUImage在macOS上直接输出32BGRA数据,绕过这个坑。实际上,上述说法对于macOS High Sierra是不成立的,其他版本的Mac没测试。

下面,以iMac 5k为例调用摄像头,设置AVCaptureVideoDataOutput.videoSettings = nil; // receives samples in device format后,返回kCVPixelFormatType_422YpCbCr8 ,即yuv422sp('2vuy')。可知,和iOS一样,iMac摄像头原始格式为yuv422p。也证明了GPUImage的说法不那么正确,也有了接下来的折腾。

好了,问题来了,macOS输出yuv格式数据有点小坑。下面描述输出为yuv420p时,通过CVMetalTextureCacheCreateTextureFromImage创建Metal纹理时返回kCVReturnFirst(-6660)的解决过程。

指定videoSettings的输出格式为yuv420p后,在CVMetalTextureCacheCreateTextureFromImage创建Metal纹理时返回kCVReturnFirst(-6660)。显然,没创建出可用的纹理。

为处理-6660,加上[videoOutput setVideoSettings:@{(id) kCVPixelBufferMetalCompatibilityKey: @(TRUE)}];反而导致CVPixelBufferGetPlaneCount(cameraFrame)返回值为0。具体原因是,每次调用setVideoSettings会覆盖上一次设置的结果。解决办法是,先构造完整videoSettings字典,再设置。比如,

#if TARGET_OS_MAC
NSDictionary<NSString *, id> *videoSettings = @{
    (id) kCVPixelBufferMetalCompatibilityKey: @(TRUE),
    (id) kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
};
videoOutput.videoSettings = videoSettings;
#else
[videoOutput setVideoSettings:@{(id) kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)}];
#endif

同理,单独指定为kCVPixelFormatType_32BGRA在CVMetalTextureCacheCreateTextureFromImage创建纹理时也是-6660,解决办法同上。

2. Capture GPU Frame

按照GPUImage的做法,在macOS High Serria且Scheme为默认值情况下,Capture GPU Frame功能不可能用。具体表现为,Xcode 9的Capture GPU Frame功能变灰、快捷图标消失。启动app也不打印Metal API Extended Validation信息。正常情况下,打印信息示例如下。

[DYMTLInitPlatform] platform initialization successful
Metal GPU Frame Capture Enabled
Metal API Validation Enabled

通常我们第一反应是,可以强制修改Scheme的GPU Frame Capture为Metal(默认为Automatically Enable)。实际上,这个改动之后,Capture GPU Frame快捷图标是出现了,然而,问题并没解决。

其实,这不是Xcode的bug,是代码逻辑不对。解决起来很简单,而且不需要强制修改Scheme的GPU Frame Capture为Metal,默认的Automatically Enable就够用了。只要代码逻辑合理,Capture GPU Frame会自动设置为可用状态。

3. Shader编译与调试

3.1 Metal shader文件不可放入Bundle

.metal文件放入Bundle中,Xcode编译时并不检查shader代码是否正确。相应地,运行后使用defaultLibrary得不到预编译的着色器函数。

3.2 在线调试看不到Shader源码

在线调试Metal shader时,发现找不到源码,Xcode提示如下所示:

Cannot show the function source
Xcode could not find the library source. Make sure debugging information is enabled for library compilation under target build settings.

具体原因是,在Metal出现前的老Xcode创建的项目一般会出现此问题。项目太古老,用Xcode 9打开后,它不会自动设置此项。

解决办法:Produce debugging information将debug设置Yes。使用Xcode 9等新版本创建Metal项目,默认将此项设置为Yes。现在,在线调试时Shader源码可正常修改、编译。

题外话,之前尝试修改Bitcode设置YES或NO,并不能解决此问题。

3.3 Metal文件不支持平台相关的条件编译

举例:

#if TARGET_OS_MAC
    XXX
#else
    YYY
#endif

在macOS上运行Metal应用,实际编译结果得到YYY。

3.4 Metal文件包含Metal文件或自定义头文件

Metal文件可包含另一个Metal文件,在Xcode 9,这是可行的。也可包含自定义头文件。缺点是Xcode无法自动跳转到相应的文件,相当不方便,希望官方后续能解决此缺陷。

3.5 cannot have global constructors (llvm.global_ctors) in FragmentFunctionXXX

从3.4节可知,Metal文件允许包含另一个Metal文件。那么,你会想,能否定义一些常用颜色转换矩阵在公共metal文件,然后在其它metal中直接使用,从而避免每次绘制都上传这些数据呢?

你问我支不支持,我当然支持你的想法。可是,也得看看编译器怎么看。实际上,如果定义的是行或列向量,这是可行的。然而,如果定义全局矩阵(比如,half3x3),而且全局矩阵参与计算,最终结果为Fragment Function的返回值,Xcode编译期间会报错:

cannot have global constructors (llvm.global_ctors) in FragmentFunctionXXX

怎么解决呢?目前来看,只能打消定义全局矩阵的念头。定义几个常用的采样器就够了,要啥自行车。

3.6 空CommandBuffer导致Capture GPU Frame无法结束

每次渲染提交不带MTLRenderCommandEncoder的MTLCommandBuffer,进行Capture GPU Frame,Xcode状态栏会疯狂读取MTLCommandBuffer数据,无法自拔。比如,定时执行如下代码。

id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
[commandBuffer commit];

为什么呢?因为代码存在逻辑错误,才会出现这种现象,上述代码只是示意。那,哪里错了呢?咱们还是用图说话吧。

小结

使用Metal 2遇到的问题不止这些。之后会整理成文档,逐步发布。不得不说,现在的Metal比2015年那会儿在工具的支持上强太多了。比如,Xcode支持断点预览纹理,从此不再频繁Capture GPU Frame。
另外,Capture GPU Frame在macOS App上的速度非常快,比iOS爽太多。放两个截图示意下。

GPU Stack
断点查看纹理内容
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容