norman784
norman784

Reputation: 2221

Swift include metal shader into library [Using swift package manager]

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

Upvotes: 6

Views: 4090

Answers (4)

scherv
scherv

Reputation: 19

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

Upvotes: 1

nightwill
nightwill

Reputation: 818

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)

Upvotes: 4

gmck
gmck

Reputation: 92

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.

Upvotes: 5

norman784
norman784

Reputation: 2221

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).

Upvotes: 0

Related Questions