6

I've always worked with Xcode projects but now I'm starting a project that I want in the future run on other platforms, so I'm using Swift Package Manager to generate the project, but I'm facing an issue, my library need to include a metal shader file (also a openGL shader file), but I'm clueless on how to accomplish this.

My project has 2 parts, the library that holds the graphic stuff and the executable that is my actual application, so I want to import my graphic library into my application, but the issue is that the metal shader file is not included in the Xcode project also seems that isn't compiled/included in the bundled files for the library so I can load at runtime and use it when needed.

Also if you think I'm doing something completely wrong just point me.

Regards

6
  • I'm not sure if I' understanding your issue - I've never used the Swift Package Manager. (1) Is your Swift "library" a Framework target? (2) Are these shaders "files" that you need to access? (3) Can you turn these files into a bundle and manually include them in the Xcode project? If your answer to all three is "Yes", I believe I can help you. Commented Jan 13, 2018 at 20:08
  • (1) The Swift library is of default type (I think can be only dynamic or static, so must be a dylib or something like that. (2) The metal file I think must be compiled directly into metallib and you don't need to reference it directly in your code, just setup the vertex function and fragment function into the metal initialization (MTLLibrary). (3) Well I'm not quite sure if the bundled files will remain if I recreate the Xcode project (when adding some new dependency I need to recreate the Xcode project (or at least I do this) Commented Jan 13, 2018 at 20:13
  • Yeah... all told I showed my inexperience with what you're doing. I thought it was different. I have projects (and Frameworks that I import) that use CIKernels that are files of GLSL code. When it's a Framework, the app simply needs to import. Sounds like I'm of no help to you, sorry. Commented Jan 13, 2018 at 20:22
  • You wrote "The metal file I think must be compiled directly into metallib and you don't need to reference it directly in your code, just setup the vertex function and fragment function into the metal initialization (MTLLibrary". So which code references it? Is it the code in the executable that has to perform metal initialization? Commented Jan 15, 2018 at 9:28
  • Its a library (the game engine) and I want to distribute it via SPM, so the shader its on the game engine. I just found that the metal shader is compiled to metallib file, so I ended up to creating a bash script, check my answer for more info, but I'm still not happy with the results, but for now I can still working on the project without need to worry to much about this, later on sure I'll find a solution or someone else come with a better approach. Commented Jan 15, 2018 at 12:41

5 Answers 5

5

In Xcode 12 (swift-tools-version:5.3) a .metal file can be used from within a Swift Package by using the MTLDevice.makeDefaultLibrary(bundle:) method.

The Swift Package should have a structure similar to:

-Sources
    -MetalShaders
        -MetalShaders.metal
        -MetalShaders.swift
-Tests
    -MetalShadersTests
        -MetalShadersTests.swift

The file MetalShaders.metal should contain the Metal source code, and the file MetalShaders.swift should contain the necessary setup code. One example for initial content of MetalShaders.swift is the following:

// A metal device for access to the GPU.
public var metalDevice: MTLDevice!

// A metal library.
public var packageMetalLibrary: MTLLibrary!

// Function to perform the initial setup for Metal processing on the GPU
public func setupMetal() {
    // Create metal device for the default GPU:
    metalDevice = MTLCreateSystemDefaultDevice()
    
    // Create the library of metal functions
    // Note: Need to use makeDefaultLibrary(bundle:) as the "normal"
    //       call makeDefaultLibrary() returns nil.
    packageMetalLibrary = try? metalDevice.makeDefaultLibrary(bundle: Bundle.module)

    // List the available Metal shader functions
    print(packageMetalLibrary.functionNames)

    //
    // ... add additional setup code here ...
    // 

}

With this approach, the packageMetalLibrary variable can then be accessed from the Xcode project that has a dependency on the Swift Package by importing the target from the swift package the same way that other frameworks or packages are imported.

import MetalShaders

It may be possible to use this approach with earlier versions of Xcode as the method MTLDevice.makeDefaultLibrary(bundle:) is available since iOS 10 / macOS 10.12, but the Bundle.module extension would need to be implemented and it is not clear if this will work.

Sign up to request clarification or add additional context in comments.

Comments

4

Put your Shaders.metal file to Metal directory. Then add recources to your target in Package.swift

.target(
   name: "TargetName",
   dependencies: [
     .product(name: "AnotherModule", package: "AnotherPackage")
   ],
   resources: [
     .copy("Metal/")
   ]
)

Now you can access to your library:

let url = Bundle.module.url(forResource: "Shaders", withExtension: "metal", subdirectory: "Metal")!
let source = try String(contentsOf: url)
let library = try device.makeLibrary(source: source, options: nil)

Or even better (but only for a single shader file):

.target(
   name: "TargetName",
   dependencies: [
     .product(name: "AnotherModule", package: "AnotherPackage")
   ],
   resources: [
     .process("Metal/Shaders.metal")
   ]
)

Now you can access to your library:

let library = try device.makeDefaultLibrary(bundle: Bundle.module)

2 Comments

This is the way
This is not a great answer. Preventing pre-compilation of the metallib will prevent some amount of install time optimization and degrade run time performance. The Xcode 12 answer by gmck should be strongly preferred.
1
let source = """

    #include <metal_stdlib>
    using namespace metal;

    struct Vertex {...
"""
let library = try device.makeLibrary(source: source options: nil)
print(library.functionNames)

Metal's class allow to build shader library from String at runtime. https://developer.apple.com/documentation/metal/libraries Hoping it helps.

UPD Now it is available in a native way https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package

2 Comments

please explain your answer briefly
not here edit your answer and add the necessary description
1

To load for SwiftUI shader from SPM.

Package.swift:

.target(
    name: "Target",
    resources: [
        .copy("MetalShaders/Wave.metal"),
    ]
),

View:

fileprivate struct WaveModifier: ViewModifier {

    let library = ShaderLibrary.bundle(.module) // <-- Set `module`

    // ... 
 
    func body(content: Content) -> some View {
       TimelineView(.animation) { context in
          effect(content: content, time: context.date.timeIntervalSince(startDate)*speed)
       }
    }
 
    func effect(content: Content, time: CGFloat) -> some View {
        content
            .distortionEffect(
                library.wave( // <-- Call your function
                    .float(length),
                    .float(amplitude),
                    .float(time)),
                maxSampleOffset: .init(
                    width: 0,
                    height: amplitude))
    }
}

The code from the article about waving shader

Comments

0

Seems that right now isn't possible to do this. I'll update this when find a workaround or Apple fix this issue.

https://bugs.swift.org/browse/SR-2866

Edit: a temporary solution that I found was create an bash script that edit the workspace settings to change the build path and then compile/copy the metallib file to the executable path (didn't tried yet to see how to work distributing the library through SPM or something like that, right now only works with the code in the same folder (i.e. Sources/MyEngine <- (this folder has the metal shader), Sources/MyGame).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.