本文檔記錄Metal 2配合Xcode 9在macOS High Serria、iOS 8+開發(fā)過程遇到的攝像頭榴嗅、Capture GPU Frame與Shader編譯調(diào)試問題及解決辦法妄呕。另外,修正了GPUImage源碼中對Mac攝像頭不支持yuv輸出的“不恰當(dāng)”地說法(至少在macOS High Serria是不恰當(dāng)?shù)模?/p>
1. 調(diào)用iMac攝像頭
1.1 攝像頭的position屬性為AVCaptureDevicePositionUnspecified
在iOS開發(fā)中嗽测,一般通過AVCaptureDevicePosition(Front或Back)確認(rèn)訪問前后置攝像頭绪励。雖然iMac有前置攝像頭,然而唠粥,它的position屬性為nil疏魏。因此,當(dāng)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;
}
或者只厘,如果做平臺條件編譯,直接用默認(rèn)攝像頭即可舅巷,示例代碼如下所示羔味。
#if TARGET_OS_MAC
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
#endif
正常情況下,iMac 5k攝像頭的輸出信息如下所示钠右。
<AVCaptureDALDevice: 0x10071e6f0 [FaceTime HD Camera (Built-in)][0x1440000005ac8511]>
1.2 輸出yuv像素格式并兼容Metal 2
因?yàn)锳VFoundation和Metal 2支持macOS和iOS赋元,所以我將音視頻輸入部分寫成同一份源碼并加上適當(dāng)?shù)臈l件編譯。出于性能考慮,iOS一般讓攝像頭輸出yuv像素數(shù)據(jù)搁凸,并且在yuv空間上做些圖像處理操作媚值,這就得測試yuv轉(zhuǎn)rgb的shader是否正常工作的。因此护糖,令macOS輸出yuv格式數(shù)據(jù)在此場合是合理的杂腰。閱讀GPUImage源碼,可發(fā)現(xiàn)如下注釋:
// 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
屬性椅文,發(fā)現(xiàn)其支持yuv420sp、yuv422sp和RGBA及BGRA惜颇。
因此皆刺,GPUImage在macOS上直接輸出32BGRA數(shù)據(jù),繞過這個坑凌摄。實(shí)際上羡蛾,上述說法對于macOS High Sierra是不成立的,其他版本的Mac沒測試锨亏。
下面痴怨,以iMac 5k為例調(diào)用攝像頭,設(shè)置AVCaptureVideoDataOutput.videoSettings = nil; // receives samples in device format
后器予,返回kCVPixelFormatType_422YpCbCr8 浪藻,即yuv422sp('2vuy')∏瑁可知爱葵,和iOS一樣,iMac攝像頭原始格式為yuv422p反浓。也證明了GPUImage的說法不那么正確萌丈,也有了接下來的折騰。
好了雷则,問題來了辆雾,macOS輸出yuv格式數(shù)據(jù)有點(diǎn)小坑。下面描述輸出為yuv420p時月劈,通過CVMetalTextureCacheCreateTextureFromImage
創(chuàng)建Metal紋理時返回kCVReturnFirst(-6660)的解決過程度迂。
指定videoSettings的輸出格式為yuv420p后,在CVMetalTextureCacheCreateTextureFromImage
創(chuàng)建Metal紋理時返回kCVReturnFirst(-6660)艺栈。顯然英岭,沒創(chuàng)建出可用的紋理。
為處理-6660湿右,加上[videoOutput setVideoSettings:@{(id) kCVPixelBufferMetalCompatibilityKey: @(TRUE)}];
反而導(dǎo)致CVPixelBufferGetPlaneCount(cameraFrame)
返回值為0诅妹。具體原因是,每次調(diào)用setVideoSettings會覆蓋上一次設(shè)置的結(jié)果。解決辦法是吭狡,先構(gòu)造完整videoSettings字典尖殃,再設(shè)置。比如划煮,
#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
同理送丰,單獨(dú)指定為kCVPixelFormatType_32BGRA在CVMetalTextureCacheCreateTextureFromImage
創(chuàng)建紋理時也是-6660,解決辦法同上弛秋。
2. Capture GPU Frame
按照GPUImage的做法器躏,在macOS High Serria且Scheme為默認(rèn)值情況下,Capture GPU Frame功能不可能用蟹略。具體表現(xiàn)為登失,Xcode 9的Capture GPU Frame功能變灰、快捷圖標(biāo)消失挖炬。啟動app也不打印Metal API Extended Validation信息揽浙。正常情況下,打印信息示例如下意敛。
[DYMTLInitPlatform] platform initialization successful
Metal GPU Frame Capture Enabled
Metal API Validation Enabled
通常我們第一反應(yīng)是馅巷,可以強(qiáng)制修改Scheme的GPU Frame Capture為Metal(默認(rèn)為Automatically Enable)。實(shí)際上草姻,這個改動之后钓猬,Capture GPU Frame快捷圖標(biāo)是出現(xiàn)了,然而撩独,問題并沒解決逗噩。
其實(shí),這不是Xcode的bug跌榔,是代碼邏輯不對异雁。解決起來很簡單,而且不需要強(qiáng)制修改Scheme的GPU Frame Capture為Metal僧须,默認(rèn)的Automatically Enable就夠用了纲刀。只要代碼邏輯合理,Capture GPU Frame會自動設(shè)置為可用狀態(tài)担平。
3. Shader編譯與調(diào)試
3.1 Metal shader文件不可放入Bundle
.metal文件放入Bundle中示绊,Xcode編譯時并不檢查shader代碼是否正確。相應(yīng)地暂论,運(yùn)行后使用defaultLibrary得不到預(yù)編譯的著色器函數(shù)面褐。
3.2 在線調(diào)試看不到Shader源碼
在線調(diào)試Metal shader時,發(fā)現(xiàn)找不到源碼取胎,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出現(xiàn)前的老Xcode創(chuàng)建的項(xiàng)目一般會出現(xiàn)此問題湃窍。項(xiàng)目太古老,用Xcode 9打開后匪傍,它不會自動設(shè)置此項(xiàng)您市。
解決辦法:Produce debugging information將debug設(shè)置Yes。使用Xcode 9等新版本創(chuàng)建Metal項(xiàng)目役衡,默認(rèn)將此項(xiàng)設(shè)置為Yes∫鹦荩現(xiàn)在,在線調(diào)試時Shader源碼可正常修改手蝎、編譯榕莺。
題外話,之前嘗試修改Bitcode設(shè)置YES或NO棵介,并不能解決此問題帽撑。
3.3 Metal文件不支持平臺相關(guān)的條件編譯
舉例:
#if TARGET_OS_MAC
XXX
#else
YYY
#endif
在macOS上運(yùn)行Metal應(yīng)用,實(shí)際編譯結(jié)果得到Y(jié)YY鞍时。
3.4 Metal文件包含Metal文件或自定義頭文件
Metal文件可包含另一個Metal文件,在Xcode 9扣蜻,這是可行的逆巍。也可包含自定義頭文件。缺點(diǎn)是Xcode無法自動跳轉(zhuǎn)到相應(yīng)的文件莽使,相當(dāng)不方便锐极,希望官方后續(xù)能解決此缺陷。
3.5 cannot have global constructors (llvm.global_ctors) in FragmentFunctionXXX
從3.4節(jié)可知芳肌,Metal文件允許包含另一個Metal文件灵再。那么,你會想亿笤,能否定義一些常用顏色轉(zhuǎn)換矩陣在公共metal文件翎迁,然后在其它metal中直接使用,從而避免每次繪制都上傳這些數(shù)據(jù)呢净薛?
你問我支不支持汪榔,我當(dāng)然支持你的想法∷喟荩可是痴腌,也得看看編譯器怎么看。實(shí)際上燃领,如果定義的是行或列向量士聪,這是可行的。然而猛蔽,如果定義全局矩陣(比如剥悟,half3x3),而且全局矩陣參與計算,最終結(jié)果為Fragment Function的返回值懦胞,Xcode編譯期間會報錯:
cannot have global constructors (llvm.global_ctors) in FragmentFunctionXXX
怎么解決呢替久?目前來看,只能打消定義全局矩陣的念頭躏尉。定義幾個常用的采樣器就夠了蚯根,要啥自行車。
3.6 空CommandBuffer導(dǎo)致Capture GPU Frame無法結(jié)束
每次渲染提交不帶MTLRenderCommandEncoder的MTLCommandBuffer胀糜,進(jìn)行Capture GPU Frame颅拦,Xcode狀態(tài)欄會瘋狂讀取MTLCommandBuffer數(shù)據(jù),無法自拔教藻。比如距帅,定時執(zhí)行如下代碼。
id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
[commandBuffer commit];
為什么呢括堤?因?yàn)榇a存在邏輯錯誤碌秸,才會出現(xiàn)這種現(xiàn)象,上述代碼只是示意悄窃。那讥电,哪里錯了呢?咱們還是用圖說話吧轧抗。
小結(jié)
使用Metal 2遇到的問題不止這些恩敌。之后會整理成文檔,逐步發(fā)布横媚。不得不說纠炮,現(xiàn)在的Metal比2015年那會兒在工具的支持上強(qiáng)太多了。比如灯蝴,Xcode支持?jǐn)帱c(diǎn)預(yù)覽紋理恢口,從此不再頻繁Capture GPU Frame。
另外穷躁,Capture GPU Frame在macOS App上的速度非郴∮快,比iOS爽太多折砸。放兩個截圖示意下看疗。