git.iko.soy/metal-metaballs
on master help sign in
Added code to avoid rendering lag.
8961c85vinivendra
PixelImage/MetalMetaballRenderer.swift+35−11
@@ -9,6 +9,14 @@
9import Metal
9import Metal
10import UIKit
10import UIKit
11
11
12+class TextureRef {
13+ var texture: MTLTexture
14+
15+ init(texture: MTLTexture) {
16+ self.texture = texture
17+ }
18+}
19+
12class MetalMetaballRenderer: MetaballRenderer {
20class MetalMetaballRenderer: MetaballRenderer {
13
21
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
23
31
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>!
27
35
28 let dataSource: MetaballDataSource
36 let dataSource: MetaballDataSource
29
37
38+ var dirty = false
39+ var metalIsBusy = false
40+
30 required init(dataSource: MetaballDataSource) {
41 required init(dataSource: MetaballDataSource) {
31 self.dataSource = dataSource
42 self.dataSource = dataSource
32 }
43 }
33
44
34 func updateTargetView() {
45 func updateTargetView() {
35− let metaballs = dataSource.metaballs
36
46
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()
41 }
51 }
42
52
53+ if metalIsBusy {
54+ dirty = true
55+ return
56+ } else {
57+ metalIsBusy = true
58+ dirty = false
59+ }
60+
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
44
62
45 // Render graphics to metal texture
63 // Render graphics to metal texture
46− self.renderToRenderingTexture(metaballs: metaballs)
64+ self.renderToRenderingTexture()
65+ self.metalIsBusy = false
66+
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)
49
69
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
73+
74+ // If the image is dirty, update it
75+ if self.dirty {
76+ self.updateTargetView()
77+ }
53 })
78 })
54 }
79 }
55 }
80 }
56
81
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)
60
85
@@ -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)
64
89
65− renderingTexture = context.device.newTextureWithDescriptor(textureDescriptor)
90+ renderingTexture = TextureRef(texture: context.device.newTextureWithDescriptor(textureDescriptor))
66 }
91 }
67
92
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)
79
104
80− // Create up-to-date metaball buffer
81− let metaballInfoBuffer = metaballBuffer(metaballs)
82−
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()
85
107
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
141 }
164 }
142
165
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)