4

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")
  • Hello! Have you found a solution for [this](http://5.9.10.113/54250944/contentsscale-does-not-improve-image-quality-of-calayer-in-avvideocompositioncor)? – Dmitrii Kononets Aug 19 '21 at 05:41

3 Answers3

4

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

Frank Rupprecht
  • 9,191
  • 31
  • 56
  • 1
    Thank you, Frank! .kernel extension didn't work so I changed it to */cikernel.metal, but then your solution works perfectly. Just one question..., why we need -c option in xcrun metal? Apple's doc didn't have this option, but the script fails without it. – Giraff Wombat Aug 08 '19 at 05:05
  • According to the command line help it's `Only run preprocess, compile, and assemble steps`. And honestly I don't know why it's required, I just read the logs of what the default Metal build phase does and copied what was needed… ¯\_(ツ)_/¯ – Frank Rupprecht Aug 08 '19 at 07:00
  • d(>_・ )☆☆great! – Giraff Wombat Aug 09 '19 at 05:35
  • 1
    OMG @Frank I almost had a heart attack thinking my CIKernels were gone. But I followed the video, and your answer, and eventually got there. Both `SCNProgram` and `CIFIlters` working together. Awesome! Video:https://developer.apple.com/videos/play/wwdc2020/10021/ – Farini Apr 30 '21 at 21:40
1

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
AlterEgo
  • 129
  • 1
  • 4
  • yes,this is right way, metal compile system will ignore non .metal file, and will produce nothing. – krosshj Apr 20 '21 at 08:57
1

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

Jeshua Lacock
  • 5,730
  • 1
  • 28
  • 58