.metallib文件冲突报错与解决方案

时间:2021-7-21 作者:qvyue

在将Metal接入主工程时,发现编译时就会报错:

Multiple commands produce ‘xxx/default.metallib’:

  1. Target ‘xxx’ (project ‘xxx’): MetalLink /xxx/default.metallib
  2. That command depends on command in Target ‘xxx’ (project ‘xxx’): script phase “[CP] Copy Pods Resources”

原因:

工程中存在多个default.metallib文件,因为使用了其他生成metallib的库,因此有冲突

解决:

  1. 自行生成.metallib文件,运行时进行静态编译:

在compile_source去掉.metal文件,根据苹果的指引和官方文档来操作,先把数个.metal文件合成一个.air文件,然后再将.air文件编译成.metallib文件。

.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文件进行编译:

.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
  1. 在工程中读取metallib文件

将读取代码改为,即可进行读取:

NSString *libPath = [METAL_BUNDLE pathForResource:@"alpha_video_renderer" ofType:@"metallib"];
id  defaultLibrary = [_device newLibraryWithFile:libPath error:&error];
  1. 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的流程图:

.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文件:

.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);
}
.metallib文件冲突报错与解决方案

还可以协助报警告:

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;
}
.metallib文件冲突报错与解决方案

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

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。