swift metal: creating compute pass cycles

Viewed 46

I'm implementing a gaussian subtract function that extracts features of 2d gaussian like objects from an input image. The algorithm is as follows: inputImageX -> contrast image and threshold to 255 -> stack of sigma(n) blurred B intermittent 2D arrays -> stack of input- B(n) intermittent 2d arrays as C -> max value + index of C(n) 2D arrays as D -> draw circle with sigma(n) for all in B -> repeat cycle from C until maxvalue reaches 0.

I found some MTLFunction objects for 2D gaussian blur, and can create my own shaders for the subtract, max value and create circle shaders, but I am unsure how the MTLTexture2D objects can be cycle across multiple passes of the algorithm without writing redundant looking code in my filter class.

Can anyone point me to a link where I can figure if: 1- i can use a custom struct like a 2Dmatrix x n dimensional object to pass and apply the gaussian filter per dim 3 layer 2- How to create this cycle on the MTLPipelineState object so that each buffer between C and D uses the previously generated image

1 Answers

Here is the answer. I was trying to reinvent the wheel but found that there is a nifty metal performance shader called MPSImageKeyPoints which does all of the above nicely. The code is below, it works, just make sure you instantiate your own MTLDevice, MTLCommandQueue and MPSImageKeyPoint, as well as MTLTextures

// Start with converting the image
        let inputTexture = getMTLTexture(from: getCGImage(from: image)!)

// Create a texture descriptor to get the buffer for transforming into a format compatible with MPSImageKeyPoints 

        let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .r8Unorm, width: self.width, height: self.height, mipmapped: false)
        textureDescriptor.usage = [.shaderRead, .shaderWrite]
        let keyPoints = self.device.makeTexture(descriptor: textureDescriptor)
        
        let imageConversionBuffer = self.commandQueue!.makeCommandBuffer()!
        self.imageConversion!.encode(commandBuffer: imageConversionBuffer, sourceTexture: inputTexture, destinationTexture: keyPoints!)
        imageConversionBuffer.commit()
        imageConversionBuffer.waitUntilCompleted()
        
// Use the find key points with w*h star and 0.8 min value threshold
        
        let maxpoints = self.width*self.height
        let keyPointCountBuffer = self.device.makeBuffer(length: MemoryLayout<Int>.stride, options: .cpuCacheModeWriteCombined)
        
        let keyPointDataBuffer = self.device.makeBuffer(length: MemoryLayout<MPSImageKeypointData>.stride*maxpoints, options: .cpuCacheModeWriteCombined)
        
        let keyPointBuffer = self.commandQueue!.makeCommandBuffer()
        self.findKeyPoints!.encode(to: keyPointBuffer!, sourceTexture: keyPoints!, regions: &self.filterRegion, numberOfRegions: 1, keypointCount: keyPointCountBuffer!, keypointCountBufferOffset: 0, keypointDataBuffer: keyPointDataBuffer!, keypointDataBufferOffset: 0)
        
        // Finally run the filter
        
        keyPointBuffer!.commit()
        keyPointBuffer!.waitUntilCompleted()

        // Extract the blobs
        
        let starCount = keyPointCountBuffer!.contents().bindMemory(to: Int.self, capacity: 1)
        print("Found \(starCount.pointee) stars")
        let coordinatePointer = keyPointDataBuffer!.contents().bindMemory(to: MPSImageKeypointData.self, capacity: starCount.pointee)
        let coordinateBuffer = UnsafeBufferPointer(start: coordinatePointer, count: starCount.pointee)
        let coordinates = Array(coordinateBuffer)
        
        var results = [[Int]]()
        for i in 0..<starCount.pointee {
            let coordinate = coordinates[i].keypointCoordinate
            results.append([Int(coordinate[0]), Int(coordinate[1])])
        }
Related