I am trying to create a framework that works with METAL Api (iOS). I am pretty new to this platform and I would like to know how to build the framework to work with .metal files (I am building a static lib, not dynamic). Should they be a part of the .a file, or as a resource files in the framework bundle? Or is there an other way to do that? Thanks.
Update:
For those who tackle this - I ended up following warrenm's 1's suggested option - converted the .metal file into a string and calling newLibraryWithSource:options:error:
.
Although it is not the best in performance it allowed me to ship only one framework file, without additional resources to import. That could be useful to whoever creating framework that uses Metal, ARKit, etc with shader files.
metal files in your project, Xcode invokes these tools to build a library file that you can access in your app at run time. To compile shader source into a library without using Xcode: Use the metal tool to compile each . metal file into a single .
The Metal framework gives your app direct access to a device's graphics processing unit (GPU). With Metal, apps can leverage a GPU to quickly render complex scenes and run computational tasks in parallel.
What is a Framework? Frameworks are self-contained, reusable chunks of code and resources you can import into many apps. You can even share them across iOS, tvOS, watchOS and macOS apps. When combined with Swift's access control, frameworks help define strong, testable interfaces between code modules.
Optimize graphics and compute performance with kernels that are fine-tuned for the unique characteristics of each Metal GPU family.
There are many ways to provide Metal shaders with a static library, all with different tradeoffs. I'll try to enumerate them here.
1) Transform your .metal files into static strings that are baked into your static library.
This is probably the worst option. The idea is that you preprocess your Metal shader code into strings which are included as string literals in your static library. You would then use the newLibraryWithSource:options:error:
API (or its asynchronous sibling) to turn the source into an MTLLibrary
and retrieve the functions. This requires you to devise a process for doing the .metal
-to-string conversion, and you lose the benefit of shader pre-compilation, making the resulting application slower.
2) Ship .metal files alongside your static library and require library users to add them to their app target
All things considered, this is a decent option, though it places more of a burden on your users and exposes your Metal shader source (if that's a concern). Code in your static library can use the "default library" (newDefaultLibrary
), since the code will be compiled automatically by Xcode into the app's default.metallib
, which is embedded in the app bundle as a resource.
3) Ship a .metallib file alongside your static library
This is a good middle ground between ease-of-use, performance, and security (since it doesn't expose your shader source, only its IR). Basically, you can create a "Metal Library" target in your project, into which you put your shader code. This will produce a .metallib
file, which you can ship along with your static library and have your user embed as a resource in their app target. Your static library can load the .metallib
at runtime with the newLibraryWithData:error:
or newLibraryWithURL:error:
API. Since your shaders will be pre-compiled, creating libraries will be faster, and you'll keep the benefit of compile-time diagnostics.
As someone looking to include metal shader functions in a SceneKit / ARKit related framework, the available answers led me in the wrong direction. There is a much simpler solution that uses makeDefaultLibrary(bundle: Bundle) (iOS 10+) to access the functions included in a framework's .metal
dependencies. Adding here for people in a similar position.
TL;DR, Access a Framework's MTLLibrary like this:
//Get the framework bundle by using `Bundle(for: type(of: self))` from inside any framework class.
//Then use the bundle to define an MTLLibrary.
let frameworkBundle = Bundle(for: type(of: self))
let device = MTLCreateSystemDefaultDevice()
do {
let bundleLib = try device?.makeDefaultLibrary(bundle: frameworkBundle)
print(bundleLib.functionNames) //we can access our framework's metal functions! No build tricks/workarounds.
} catch {
print("Couldn't locate default library for bundle: \(frameworkBundle)")
print( error )
}
Xcode creates a default library of shader functions at build time by compiling .metal
dependencies. This is true of both framework targets and app targets, so the real question is, how do I access my framework’s default library?
It’s possible to access a framework’s default library using the using the makeDefaultLibrary(bundle: Bundle)
method on MTLDevice
. The sample code above shows more detail.
For Scenekit/ARKit with SCNProgram
The bundle library can be set as an SCNProgram’s library property, and then fragment and shader functions can be defined just as if the .metal file was included in the main project:
//The SCNProgram that will use our framework's metal functions
var program = SCNProgram()
//Use the framework's bundle to define an MTLLibrary.
let frameworkBundle = Bundle(for: type(of: self))
let device = MTLCreateSystemDefaultDevice()
do {
let bundleLib = try device?.makeDefaultLibrary(bundle: frameworkBundle)
//set the SCNProgram's library, and define functions as usual
program.library = bundleLib
program.fragmentFunctionName = "yourCustomFrameworkFragmentFunction"
program.vertexFunctionName = "yourCustomFrameworkVertexFunction"
} catch {
print("Couldn't locate default library for bundle: \(frameworkBundle)")
print( error )
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With