Metal: vertexFunction defined in .metal file becomes nil once setting Compiler and Linker Options for MSL cikernel

1.2k Views Asked by At

VertexFunction and FragmentFunction defined in .metal file worked nicely, but they became nil once I specified Compiler and Linker Options following Apple's doc: -fcikernel flag in the Other Metal Compiler Flags option, and -cikernel flat in MTLLINKER_FLAGS in User-Defined setting.

I need the settings above for cikernel with MSL (metal shading language). Indeed, cikernel with Core Image Kernel Language deprecated in 12.0.

How could I use both vertex/fragment Metal shader and MSL cikernel together?

let library = self.device?.makeDefaultLibrary()!
let pipeLineDescriptor = MTLRenderPipelineDescriptor()
pipeLineDescriptor.vertexFunction=library.makeFunction(name: "myVertexShader")
pipeLineDescriptor.fragmentFunction=library.makeFunction(name: "myFragmentShader")
3

There are 3 best solutions below

4
Frank Rupprecht On BEST ANSWER

I guess you have to compile your filter kernels separately instead of with your default Metal library.

To do so, you could for instance give them another file extension, like .kernel and add a custom Build Rule like so:

enter image description here

Then add a custom Build Phase that copies the compiled kernel metallibs into your app bundle:

enter image description here

To initialize the CIKernel with the correct metal source, you can do something like this:

let url = Bundle(for: type(of: self)).url(forResource: "<#name of your .kernel file#>", withExtension: "metallib")!
let data = try! Data(contentsOf: url)
let kernel = try! CIKernel(functionName: "<#kernel function name#>", fromMetalLibraryData: data)

(Note that you should remove the compiler and liker flags again from your project settings to have your other Metal sources compile properly again.)

1
AlterEgo On

An update of Frank Schlegel's answer that works in Xcode 11, where, as Giraff Wombat mentions, the metal compiler simply ignores input files that don't end in .metal. To make sure the CI kernel file stays out of the regular metal pipeline's way, I made the extension ".mcikernel" and put for the build rule script:

mkdir -p ${DERIVED_FILES_DIR}/kernels
cp ${INPUT_FILE_PATH} ${DERIVED_FILES_DIR}/${INPUT_FILE_BASE}.metal
xcrun metal -fcikernel ${DERIVED_FILES_DIR}/${INPUT_FILE_BASE}.metal -c -o ${DERIVED_FILES_DIR}/kernels/${INPUT_FILE_BASE}.air
xcrun metallib -cikernel -o ${DERIVED_FILES_DIR}/kernels/${INPUT_FILE_BASE}.metallib ${DERIVED_FILES_DIR}/kernels/${INPUT_FILE_BASE}.air
2
Jeshua Lacock On

I was not able to get either of the existing Answers working (AlterEgo answer seems incomplete, like doesn't show input/output file settings).

In any event, the WWDC video, Apple recommends the following.

Create a Metal file called something like MyKernels.ci.metal with:

#include <metal_stdlib>
using namespace metal;

#include <CoreImage/CoreImage.h>
 
extern "C" float4 do_nothing(coreimage::sample_t s) {
    return s;
}

Next add two Build Rules, both of them uncheck "Run once per architecture". Rule 1:

Source Files with names matching:
*.ci.metal

xcrun metal -c -I $MTL_HEADER_SEARCH_PATHS -fcikernel "${INPUT_FILE_PATH}" -o "${SCRIPT_OUTPUT_FILE_0}"

output files:
$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).air

Second:

Source Files with names matching:
*.ci.air

xcrun metallib -cikernel "${INPUT_FILE_PATH}" -o "${SCRIPT_OUTPUT_FILE_0}"

output files:
$(METAL_LIBRARY_OUTPUT_DIR)/$(INPUT_FILE_BASE).metallib

To use:

let url = Bundle.main.url(forResource: "MyKernels", withExtension: "ci.metallib")!
let data = try! Data(contentsOf: url)
kernel = try! CIColorKernel(functionName: "do_nothing", fromMetalLibraryData: data)

Note that other answers that suggest adding the fckernel and cikernel flags to the Build Settings are not recommended. They will cause an issue if regular Metal is being used and possibly other issues. This solution will only use those flags on files with the .ci.metal extension.

Screenshot of the Build Rules:

enter image description here