在将Metal接入主工程时,发现编译时就会报错:
Multiple commands produce ‘xxx/default.metallib’:
- Target ‘xxx’ (project ‘xxx’): MetalLink /xxx/default.metallib
- That command depends on command in Target ‘xxx’ (project ‘xxx’): script phase “[CP] Copy Pods Resources”
原因:
工程中存在多个default.metallib文件,因为使用了其他生成metallib的库,因此有冲突
解决:
- 自行生成.metallib文件,运行时进行静态编译:
在compile_source去掉.metal文件,根据苹果的指引和官方文档来操作,先把数个.metal文件合成一个.air文件,然后再将.air文件编译成.metallib文件。

根据上面的代码改了一下.sh代码:
xcrun -sdk iphoneos metal MyLibrary.metal -o MyLibrary.air
xcrun -sdk iphoneos metallib MyLibrary.air -o MyLibrary.metallib
发现iPhone 6S机型有以下问题:
pipelinestate error: Error Domain=AGXMetalA8 Code=3 “Function fragmentShader has a deployment target which is incompatible with this OS.” UserInfo={NSLocalizedDescription=Function fragmentShader has a deployment target which is incompatible with this OS.}
根据论坛的介绍,发现是没指定-mios-version-min
,导致编译时会以deployment target的版本进行编译。
另外,不同版本的iOS系统支持的Metal版本也不一样,因为我们要以最低适配来对metallib文件进行编译:

得到.sh文件:
xcrun -sdk iphoneos metal -c -target air64-apple-ios9.0 alpha_video_renderer.metal -o alpha_video_renderer.air
xcrun -sdk iphoneos metal -std=ios-metal1.1 -mios-version-min=9.0 alpha_video_renderer.air -o alpha_video_renderer.metallib
- 在工程中读取metallib文件
将读取代码改为,即可进行读取:
NSString *libPath = [METAL_BUNDLE pathForResource:@"alpha_video_renderer" ofType:@"metallib"];
id defaultLibrary = [_device newLibraryWithFile:libPath error:&error];
- ShaderString保底
如果.metallib文件还是读取失败,那可以考虑将.metal文件写进String里面,用newLibraryWithSource:options:error:
来读取,不过这种是动态编译,感觉不太好。
感觉还是有坑..如果一个工程能用多个.metal文件统一生成default.metallib就好了,但是无奈是引用了别的带Metal的文件,只能这样曲线救国了..
附:不同Metal版本支持的内容
更新于7.19
发现不少提供Metal渲染的第三方库都是直接用default.metallib,这样的话接入两个库就会有符号冲突问题,这段时间恰好自研了一个Metal链式结构库,集成在Pod里面了,为了让接入的项目不会像我之前那样被坑,所以需要自己生成一个自定义的Metallib文件。
自定义metallib文件,有三个问题:
-
一个项目中往往会有多个.metal文件,如何一次性把所有的.metal文件编译成一个.metallib?
-
生成自定义metallib文件,意味着要把所有的.metal文件从工程的Compile Source里面移除掉,如何集成到Pod里面,让.metal文件成为一个只读而不编译的文件?
-
最重要的一个问题,我们读取.metallib文件是要手动运行脚本生成的,那能不能编写一个脚本,让每次编译时都自动运行生成.metallib的脚本,而不是每次编译前都自己手动跑一次?
通过自己不断查阅资料,找到了解决方案:
1. 如何一次性把所有的.metal文件编译成一个.metallib?
我们可以回顾一下生成.metallib的流程图:

可以看到,编译脚本其实是支持多个输入的,因此多个.metal文件生成一个.metallib文件是可行的,比如,对于mix.metal和no_green.metal文件,我们可以这样来写:
xcrun -sdk iphoneos metal -c -target air64-apple-ios9.0 mix.metal -o mix.air
xcrun -sdk iphoneos metal -c -target air64-apple-ios9.0 no_green.metal -o no_green.air
xcrun -sdk iphoneos metal -std=ios-metal1.1 -mios-version-min=9.0 mix.air no_green.air -o alpha_video_renderer.metallib
rm mix.air no_green.air
可以看到,我们先根据两个.metal文件生成了对应的.air文件,再将多个.air文件合并成了一个.metallib文件,那么,对于若干个.metal文件,脚本也就迎刃而解了:
#! /bin/sh
path=$(cd "$(dirname "$0")";pwd)
cd $path
files=$(ls $path)
metal_array=()
air_array=()
index=0
if [[ -f "alpha_video_renderer.metallib" ]]; then
rm alpha_video_renderer.metallib
fi
for filename in $files
do
if [ ${filename##*.} == 'metal' ]
then
echo ${filename}
prefix=${filename%%.*}
air_array[index]=$prefix".air"
metal_array[index]=$prefix".metal"
let index++
air_command="xcrun -sdk iphoneos metal -c -target air64-apple-ios9.0 "
air_command+=$prefix".metal"
air_command+=" -o "
air_command+=$prefix".air"
$air_command
fi
done
bulid_command="xcrun -sdk iphoneos metal -std=ios-metal1.1 -mios-version-min=9.0 "
remove_command="rm "
for (( i = 0; i
上面的脚本主要流程,就是先进入存放.metal文件的文件夹,并遍历所有.metal文件,生成好所有的.air文件后,再根据字符串把所有.air文件合并成.metallib文件。
2. 如何集成到Pod里面,让.metal文件成为一个只读而不编译的文件?
这个问题比较简单了,先打开Pods => Build Phases => Compile Sources,把所有.metal文件都移除掉。
然后在.podspec声明他是Resource就可以了,然后记得将.metallib放进Bundle里面,我们就可以用Bundle来读取.metallib了。
spec.resource_bundles = {
'Metal' => ['CCAlphaVideoPlayer/MetalKit/**/*.metallib']
}
spec.subspec "MetalKit" do |sp|
sp.source_files = "CCAlphaVideoPlayer/MetalKit/**/*.{h,m,mm,c}"
sp.resources = "CCAlphaVideoPlayer/MetalKit/**/*.{metal,sh}"
end
3. 如何在编译时自动运行编译.metallib脚本?
首先我们要在工程根目录放置一个脚本,用于找到对应目录的运行脚本:
#! /bin/sh
path=$(cd "$(dirname "$0")";pwd)
cd $path
sh CCAlphaVideoPlayer/MetalKit/Shaders/compile_metal.sh
然后打开Pods => Build Phases,新建一个New Run Script Phase,需要注意的是,这个脚本要先于Copy Bundle Resources项,因为我们要先运行Build Metal的脚本,再取到.metallib文件:

Build Metal脚本就是找到根目录的脚本运行啦:
sh ../../compile_metal.sh
最后我们就得到一个自定义的.metallib生成库了~效果和default.metallib是一样的,还没有符号冲突(吐槽一下苹果,使用default.metallib导致符号冲突这个真的是让人抓狂,这就意味着开发者不能集成一个以上的Metal库了吗= =)
比如我们片段着色器返回了一个float3的值,那么我编译的时候,编译器就会自动帮我指正出来了:
fragment float4 passthroughFragment(SingleInputVertexIO fragmentInput [[stage_in]],
texture2d inputTexture [[texture(0)]])
{
constexpr sampler quadSampler;
float4 color = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate);
return float3(0,0,0);
}

还可以协助报警告:
fragment float4 passthroughFragment(SingleInputVertexIO fragmentInput [[stage_in]],
texture2d inputTexture [[texture(0)]])
{
constexpr sampler quadSampler;
float4 color = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate);
float useless;
return color;
}

虽然把.metal文件从Compile Source里面移除掉后,没有了定位到具体行的功能,但总好过符号冲突编译不过吧,不知道这个问题苹果有没有办法解决了= =