从八开始——图形渲染/Metal专题

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

2021-05-21  本文已影响0人  肠粉白粥_Hoben

在将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文件。

根据上面的代码改了一下.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
  1. 在工程中读取metallib文件

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

NSString *libPath = [METAL_BUNDLE pathForResource:@"alpha_video_renderer" ofType:@"metallib"];
id <MTLLibrary> 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文件,有三个问题:

通过自己不断查阅资料,找到了解决方案:

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

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 < index; i++ )); do
    bulid_command+=${air_array[i]}
    bulid_command+=" "
    remove_command+=${air_array[i]}
    remove_command+=" "
done

bulid_command+="-o alpha_video_renderer.metallib"
$bulid_command
$remove_command

# 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文件的文件夹,并遍历所有.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/CCAlphaVideoPlayer/MetalKit/Shaders

if [[ -f "compile_metal.sh" ]]; then
sh compile_metal.sh
fi

然后打开Pods => Build Phases,新建一个New Run Script Phase,需要注意的是,这个脚本要先于Copy Bundle Resources项,因为我们要先运行Build Metal的脚本,再取到.metallib文件:

Build Metal脚本就是找到根目录的脚本运行啦:

if [[ -f "../../compile_metal.sh" ]]; then
sh ../../compile_metal.sh
fi

最后我们就得到一个自定义的.metallib生成库了~效果和default.metallib是一样的,还没有符号冲突(吐槽一下苹果,使用default.metallib导致符号冲突这个真的是让人抓狂,这就意味着开发者不能集成一个以上的Metal库了吗= =)

比如我们片段着色器返回了一个float3的值,那么我编译的时候,编译器就会自动帮我指正出来了:

fragment float4 passthroughFragment(SingleInputVertexIO fragmentInput [[stage_in]],
                                   texture2d<float> 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<float> inputTexture [[texture(0)]])
{
    constexpr sampler quadSampler;
    float4 color = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate);
    
    float useless;
    
    return color;
}

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


更新于8.24

发现自己手动在Build Phases手动更改脚本会有问题,每次Pod Update完之后脚本都会自动消失掉,因此需要把脚本放到.podspec中:

  build_metal_script = <<-CMD
      #Pods目录
      podsPath=$(pwd)
      if [[ -f "../../compile_metal.sh" ]]; then
      sh ../../compile_metal.sh
      fi
  CMD
spec.script_phase = { :name => 'Build Metal', :script => build_metal_script, :shell_path =>'/bin/sh', :execution_position => :before_compile}

注意该脚本需要指定参数before_compile,代表编译前执行,还需要放到source_files前面,否则编译失败后,因为已经执行了移除.metallib脚本,会导致.metallib文件找不到从而无法继续编译,因此需要先执行脚本再寻找.metallib。

最后Pod update一下,即可在Build Phases看到自动编译脚本啦

但这样还是有点问题,原因是指定了before_compile参数之后,会发现Bundle文件的Build会优先于Pod工程的Build,由于.metallib文件是Pod工程的脚本编译前生成的,而Bundle生成时会直接取旧的.metallib文件,因此修改.metal代码并不会直接生效,而是要再Build一次。。

在搜索资料无果之后,只能去CocoaPod的GitHub提了个issue,但作者也说目前暂不支持在Bundle文件构建之前运行自定义脚本,他会在1.11版本新增两个参数的支持,也只能期待那个时候新加的参数能解决这个问题了吧~

上一篇下一篇

猜你喜欢

热点阅读