MTKView glitches/strobing while using a custom blur filter written in Metal

408 Views Asked by At

I am using CADisplayLink to live filter an image and showing it in the MTKView. All filters work fine until I try the blur filter - during that filter MTKView sometimes starts strobing, glitching or just showing a black screen on some of the frames instead of the actual result image.

I have three interesting observations:

1) There is no such problem when I display the result image in the UIImageView, so the filter itself is not the cause of the problem

2) If I switch filter back from blur to any other filter, the same problem starts happening in those filters too, but ONLY when I used the blur filter first

3) The glitching itself is slowly fading away the more I use the app. It even starts to occur less and less the more times I actually launch the app.

Code for the MTKView:

import GLKit
import UIKit
import MetalKit
import QuartzCore

class MetalImageView: MTKView
{
    let colorSpace = CGColorSpaceCreateDeviceRGB()

    lazy var commandQueue: MTLCommandQueue =
    {
        [unowned self] in

        return self.device!.makeCommandQueue()!
    }()

    lazy var ciContext: CIContext =
    {
        [unowned self] in

        return CIContext(mtlDevice: self.device!)
    }()

    override init(frame frameRect: CGRect, device: MTLDevice?)
    {
        super.init(frame: frameRect,
            device: device ?? MTLCreateSystemDefaultDevice())

        if super.device == nil
        {
            fatalError("Device doesn't support Metal")
        }

        framebufferOnly = false
    }

    required init(coder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }

    // from tutorial
    private func setup() {
        framebufferOnly = false
        isPaused = false
        enableSetNeedsDisplay = false
    }

    /// The image to display
    var image: CIImage?
    {
        didSet
        {

        }
    }

    override func draw()
    {
        guard let
            image = image,
            let targetTexture = currentDrawable?.texture else
        {
            return
        }


        let commandBuffer = commandQueue.makeCommandBuffer()

        let bounds = CGRect(origin: CGPoint.zero, size: drawableSize)

        let originX = image.extent.origin.x
        let originY = image.extent.origin.y

        let scaleX = drawableSize.width / image.extent.width
        let scaleY = drawableSize.height / image.extent.height
        let scale = min(scaleX, scaleY)

        let scaledImage = image
            .transformed(by: CGAffineTransform(translationX: -originX, y: -originY))
            .transformed(by: CGAffineTransform(scaleX: scale, y: scale))

        ciContext.render(scaledImage,
            to: targetTexture,
            commandBuffer: commandBuffer,
            bounds: bounds,
            colorSpace: colorSpace)

        commandBuffer!.present(currentDrawable!)
        commandBuffer!.commit()

        super.draw()

    }
}


extension CGRect
{
    func aspectFitInRect(target: CGRect) -> CGRect
    {
        let scale: CGFloat =
        {
            let scale = target.width / self.width

            return self.height * scale <= target.height ?
                scale :
                target.height / self.height
        }()

        let width = self.width * scale
        let height = self.height * scale
        let x = target.midX - width / 2
        let y = target.midY - height / 2

        return CGRect(x: x,
            y: y,
            width: width,
            height: height)
    }
}

The code for the blur filter in Metal:

float4 zoneBlur(sampler src, float time, float4 touch) {

        float focusPower = 2.0;
        int focusDetail = 10;

        float2 uv = src.coord();
        float2 fingerPos;
        float2 size = src.size();


        if (touch.x == 0 || touch.y == 0) {
            fingerPos = float2(0.5, 0.5);

        } else {
            fingerPos = touch.xy / size.xy;
        }


        float2 focus = uv - fingerPos;

        float4 outColor;
        outColor = float4(0, 0, 0, 1);


        for (int i=0; i < focusDetail; i++) {
            float power = 1.0 - focusPower * (1.0/size.x) * float(i);
            outColor.rgb += src.sample(focus * power + fingerPos).rgb;
        }

        outColor.rgb *= 1.0 / float(focusDetail);
        return outColor;
    }

I wonder what can cause such an odd behaviour?

0

There are 0 best solutions below