swiftmetal

Metal runtime shader library compilation and linking issue


In my project I need to do the following:

  1. In runtime create metal Dynamic library from source.
  2. In runtime create metal Executable library from source and Link it with my previous created dynamic library.
  3. Create compute pipeline using those two libraries created above.

But I get the following error at the third step:

Error Domain=AGXMetalG15X_M1 Code=2 "Undefined symbols:
  _Z5noisev, referenced from: OnTheFlyKernel
" UserInfo={NSLocalizedDescription=Undefined symbols:
  _Z5noisev, referenced from: OnTheFlyKernel
}

This error won't appear when I create and link these libraries from the command line.


import Foundation
import Metal

class MetalShaderCompiler {
    let device = MTLCreateSystemDefaultDevice()!
    var pipeline: MTLComputePipelineState!
    
    func compileDylib() -> MTLDynamicLibrary {
        let source = """
        #include <metal_stdlib>
        using namespace metal;
        
        half3 noise() {
            return half3(1, 0, 1);
        }
        """
        let option = MTLCompileOptions()
        option.libraryType = .dynamic
        option.installName = "@executable_path/libFoundation.metallib"
        let library = try! device.makeLibrary(source: source, options: option)
        let dylib = try! device.makeDynamicLibrary(library: library)
        return dylib
    }
    
    func compileExlib(dylib: MTLDynamicLibrary) -> MTLLibrary {
        let source = """
        #include <metal_stdlib>
        using namespace metal;
        extern half3 noise();
        kernel void OnTheFlyKernel(texture2d<half, access::read> src [[texture(0)]],
                               texture2d<half, access::write> dst [[texture(1)]],
                               ushort2 gid [[thread_position_in_grid]]) {
        
            half4 rgba = src.read(gid);
        
            rgba.rgb += noise();
        
            dst.write(rgba, gid);
        }
        """
        let option = MTLCompileOptions()
        option.libraryType = .executable
        option.libraries = [dylib]
        let library = try! self.device.makeLibrary(source: source, options: option)
        return library
    }
    
    func runtime() {
        let dylib = self.compileDylib()
        let exlib = self.compileExlib(dylib: dylib)
        
        let pipelineDescriptor = MTLComputePipelineDescriptor()
        pipelineDescriptor.computeFunction = exlib.makeFunction(name: "OnTheFlyKernel")
        pipelineDescriptor.preloadedLibraries = [dylib]
        
        pipeline = try! device.makeComputePipelineState(descriptor: pipelineDescriptor, options: .bindingInfo, reflection: nil)
    }
}

Solution

  • You should really be using a metal file to define the source as the compiler will help catch any errors and gets rid of the source code strings.

    That aside I think your error is noted in the docs heremake library

    note the discussion "Because there’s no search path to find other functions, the source may only import the Metal default library."

    Your main file can be reduced as

    class MetalShaderCompiler {
    let device = MTLCreateSystemDefaultDevice()!
    var pipeline: MTLComputePipelineState!
    
    func runtime() {
        
        let pipelineDescriptor = MTLComputePipelineDescriptor()
        pipelineDescriptor.computeFunction = self.device.makeDefaultLibrary()!.makeFunction(name: "OnTheFlyKernel")
        do {
            pipeline = try device.makeComputePipelineState(descriptor: pipelineDescriptor, options: .bindingInfo, reflection: nil)
        } catch {
            print(error)
        }
    }
    

    }

    Your .metal file could be in one or more files depending on your specific needs.

    #include <metal_stdlib>
    using namespace metal;
    
    half3 noise() {
        return half3(1, 0, 1);
    }
    
    kernel void OnTheFlyKernel(texture2d<half, access::read> src [[texture(0)]],
                               texture2d<half, access::write> dst [[texture(1)]],
                               ushort2 gid [[thread_position_in_grid]]) {
        
        half4 rgba = src.read(gid);
        
        rgba.rgb += noise();
        
        dst.write(rgba, gid);
    }
    

    UPDATE

    The issue lies here

    UserInfo={NSLocalizedDescription=Unable to resolve install name ”@executable_path/libFoundation.metallib” (embedded in ”<no URL>”, loading from executable directory ”<no URL>”): @executable_path cannot be used in a library that has not been loaded from file URL}
    

    So you'll have to load that source from a url if you want to use it as an external library I guess..