@@ -9,6 +9,14 @@
9import Metal
9import Metal
10import UIKit
10import UIKit
13+ var texture: MTLTexture
15+ init(texture: MTLTexture) {
16+ self.texture = texture
12class MetalMetaballRenderer: MetaballRenderer {
20class MetalMetaballRenderer: MetaballRenderer {
14 typealias TargetView = UIImageView
22 typealias TargetView = UIImageView
@@ -22,17 +30,19 @@ class MetalMetaballRenderer: MetaballRenderer {
22 var previousSize = CGSize.zero
30 var previousSize = CGSize.zero
24 let context = MTLContext()
32 let context = MTLContext()
25− var renderingTexture: MTLTexture!
33+ var renderingTexture: TextureRef!
26 var imageBuffer: UnsafeMutablePointer<Void>!
34 var imageBuffer: UnsafeMutablePointer<Void>!
28 let dataSource: MetaballDataSource
36 let dataSource: MetaballDataSource
39+ var metalIsBusy = false
30 required init(dataSource: MetaballDataSource) {
41 required init(dataSource: MetaballDataSource) {
31 self.dataSource = dataSource
42 self.dataSource = dataSource
34 func updateTargetView() {
45 func updateTargetView() {
35− let metaballs = dataSource.metaballs
37 // Re-alloc buffers if the size has changed
47 // Re-alloc buffers if the size has changed
38 if previousSize != targetView.size {
48 if previousSize != targetView.size {
@@ -40,21 +50,36 @@ class MetalMetaballRenderer: MetaballRenderer {
40 updateFrame()
50 updateFrame()
43 dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) { () -> Void in
61 dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) { () -> Void in
45 // Render graphics to metal texture
63 // Render graphics to metal texture
46− self.renderToRenderingTexture(metaballs: metaballs)
64+ self.renderToRenderingTexture()
65+ self.metalIsBusy = false
47 // Transform metal texture into image
67 // Transform metal texture into image
48− let uiimage = self.image(texture: self.renderingTexture)
68+ let uiimage = self.image(texture: self.renderingTexture.texture)
50 dispatch_async(dispatch_get_main_queue(), { () -> Void in
70 dispatch_async(dispatch_get_main_queue(), { () -> Void in
51 // Send image to view
71 // Send image to view
52 self.targetView.image = uiimage
72 self.targetView.image = uiimage
74+ // If the image is dirty, update it
76+ self.updateTargetView()
57− func renderToRenderingTexture(metaballs metaballs: [Metaball]) {
82+ func renderToRenderingTexture() {
58 let width = Int(targetView.width)
83 let width = Int(targetView.width)
59 let height = Int(targetView.height)
84 let height = Int(targetView.height)
@@ -62,7 +87,7 @@ class MetalMetaballRenderer: MetaballRenderer {
62 let textureDescriptor =
87 let textureDescriptor =
63 MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.BGRA8Unorm, width: width, height: height, mipmapped: false)
88 MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.BGRA8Unorm, width: width, height: height, mipmapped: false)
65− renderingTexture = context.device.newTextureWithDescriptor(textureDescriptor)
90+ renderingTexture = TextureRef(texture: context.device.newTextureWithDescriptor(textureDescriptor))
68 let kernelFunction: MTLFunction?
93 let kernelFunction: MTLFunction?
@@ -77,15 +102,13 @@ class MetalMetaballRenderer: MetaballRenderer {
77 let threadGroups = MTLSizeMake(width / threadGroupCounts.width,
102 let threadGroups = MTLSizeMake(width / threadGroupCounts.width,
78 height / threadGroupCounts.height, 1)
103 height / threadGroupCounts.height, 1)
80− // Create up-to-date metaball buffer
81− let metaballInfoBuffer = metaballBuffer(metaballs)
83 // Send commands to metal and render
105 // Send commands to metal and render
84 let commandBuffer = context.commandQueue.commandBuffer()
106 let commandBuffer = context.commandQueue.commandBuffer()
86 let commandEncoder = commandBuffer.computeCommandEncoder()
108 let commandEncoder = commandBuffer.computeCommandEncoder()
87 commandEncoder.setComputePipelineState(pipeline)
109 commandEncoder.setComputePipelineState(pipeline)
88− commandEncoder.setTexture(renderingTexture, atIndex: 0)
110+ commandEncoder.setTexture(renderingTexture.texture, atIndex: 0)
111+ let metaballInfoBuffer = metaballBuffer()
89 commandEncoder.setBuffer(metaballInfoBuffer, offset: 0, atIndex: 0)
112 commandEncoder.setBuffer(metaballInfoBuffer, offset: 0, atIndex: 0)
90 commandEncoder.dispatchThreadgroups(threadGroups,
113 commandEncoder.dispatchThreadgroups(threadGroups,
91 threadsPerThreadgroup: threadGroupCounts)
114 threadsPerThreadgroup: threadGroupCounts)
@@ -140,10 +163,11 @@ class MetalMetaballRenderer: MetaballRenderer {
140 return image
163 return image
143− func metaballBuffer(metaballs: [Metaball]) -> MTLBuffer {
166+ func metaballBuffer() -> MTLBuffer {
144 // Create Float array for buffer
167 // Create Float array for buffer
145 // Exclude metaballs far from the view's bounds
168 // Exclude metaballs far from the view's bounds
146 let border: CGFloat = 100
169 let border: CGFloat = 100
170+ let metaballs = self.dataSource.metaballs
147 var floats: [Float] = metaballs.reduce([Float(0)]) { (var array, metaball) -> [Float] in
171 var floats: [Float] = metaballs.reduce([Float(0)]) { (var array, metaball) -> [Float] in
148 let x = CGFloat(metaball.midX)
172 let x = CGFloat(metaball.midX)
149 let y = CGFloat(metaball.midY)
173 let y = CGFloat(metaball.midY)