Metal Kernel Functions as Core Image Filter Engines

DZone 's Guide to

Metal Kernel Functions as Core Image Filter Engines

Using Metal compute functions is way more efficient but requires boilerplate code. Simon Gladman helps you avoid this with his MetalFilter.

· Mobile Zone ·
Free Resource
Whoa! A CoreImage filter for generating Perlin Noise based on a Metal compute function.

The chapter in my book, Core Image for Swift, that discusses creating custom filters and writing GLSL has become one of the largest and the most enjoyable to write. I've written a handful of new filters which I posted about recently. However, writing Core Image kernels is a bit of a pain since the GLSL code is passed to CIKernel as strings and the tooling is a tad lacking. Here's the carefully constructed super simple threshold code:

let thresholdKernel = CIColorKernel(string:
        "kernel vec4 thresholdFilter(__sample image, float threshold)" +
        "{" +
        "   float luma = dot(image.rgb, vec3(0.2126, 0.7152, 0.0722));" +

        "   return (luma > threshold) ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 0.0);" +

No compile time checking - ugh! Eyes all mangled up with horrible quotes and string concatenation "+" signs all over the shop - ugh! That's just a simple one too!

Using Metal compute functions would be a great alternative, but implementing them requires a load of boilerplate code and we may want to chain together existing Core Image filters with custom Metal shaders. 

Well, those problems are over! My MetalFilter class extends CIFilter and hides away all the Metal setup code. As a developer, all you need to do is write your Metal kernel and extend MetalFilter like so:

    class MetalKuwaharaFilter: MetalFilter
            super.init(functionName: "kuwahara")

        required init?(coder aDecoder: NSCoder)
            fatalError("init(coder:) has not been implemented")

        var inputRadius: CGFloat = 15

        override func setDefaults()
            inputRadius = 15

        override var attributes: [String : AnyObject]
            return [
                kCIAttributeFilterDisplayName: "Metal Kuwahara",

                "inputImage": [kCIAttributeIdentity: 0,
                    kCIAttributeClass: "CIImage",
                    kCIAttributeDisplayName: "Image",
                    kCIAttributeType: kCIAttributeTypeImage],

                "inputRadius": [kCIAttributeIdentity: 0,
                    kCIAttributeClass: "NSNumber",
                    kCIAttributeDefault: 15,
                    kCIAttributeDisplayName: "Radius",
                    kCIAttributeMin: 0,
                    kCIAttributeSliderMin: 0,
                    kCIAttributeSliderMax: 30,
                    kCIAttributeType: kCIAttributeTypeScalar],

After registering it, it behaves much like any other Core Image filter!

Performance seems to vary. Because of the setup overhead, the first call isn't massively faster than a GLSL based CIKernel, but after that, the Metal based filter can be up to 50% faster. As a demonstration, I've created two implementations of the Kuwahara filter - one in Metal and one in GLSL and plugged them both into Filterpedia (both live under the "Custom Filters" category). 

For a more detailed discussion, you'll have to wait for my book, Core Image for Swift, which should be available next month and, with fingers crossed, should be on pre-order next week!

In the meantime, if you'd like to read more on writing filters using Metal, here's a little introduction I wrote last year: Image Processing with Metal.


Published at DZone with permission of Simon Gladman , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}