git.iko.soy/metal-metaballs
on master help sign in
Fixing merge issues.
commitb3f0035f91b87b4aba3262ccc63182eb71087890 authorvinivendra <vinivendra@gmail.com> dateWed 10 Feb 2016, 14:07 (11 years ago) parent83f58c3f7ae50c
4 files changed+94−46
PixelImage/ImageMetaballRenderer.swift+12−4 PixelImage/MetaballRenderer.swift+4−6 PixelImage/MetalMetaballRenderer.swift+71−32 PixelImage/ViewController.swift+7−4
PixelImage/ImageMetaballRenderer.swift+12−4
@@ -4,9 +4,17 @@ import UIKit
44class ImageMetaballRenderer: MetaballRenderer {
55 typealias TargetView = UIImageView
66
7− static func updateTargetView(imageView: TargetView, dataSource: MetaballDataSource) {
8− let width = Int(imageView.width)
9− let height = Int(imageView.height)
7+ let targetView = TargetView()
8+
9+ let dataSource: MetaballDataSource
10+
11+ required init(dataSource: MetaballDataSource) {
12+ self.dataSource = dataSource
13+ }
14+
15+ func updateTargetView() {
16+ let width = Int(targetView.width)
17+ let height = Int(targetView.height)
1018 let metaballs = dataSource.metaballs
1119
1220 let bytesPerPixel = 4;
@@ -57,7 +65,7 @@ class ImageMetaballRenderer: MetaballRenderer {
5765
5866 let cgImage = CGBitmapContextCreateImage(context)
5967 let image = UIImage(CGImage: cgImage!)
60− imageView.image = image
68+ targetView.image = image
6169 }
6270}
6371
PixelImage/MetaballRenderer.swift+4−6
@@ -8,11 +8,9 @@ protocol MetaballDataSource {
88protocol MetaballRenderer {
99 typealias TargetView: UIView
1010
11− static func updateTargetView(targetView: TargetView, dataSource: MetaballDataSource)
12−}
11+ var targetView: TargetView { get }
12+
13+ init(dataSource: MetaballDataSource)
1314
14−extension MetaballRenderer {
15− static func createTargetView(frame frame: CGRect) -> TargetView {
16− return TargetView(frame: frame)
17− }
15+ func updateTargetView()
1816}
PixelImage/MetalMetaballRenderer.swift+71−32
@@ -1,7 +1,12 @@
1−// TODO: Render metaballs on layer/view instead of image
1+// TODO: Optimize resources (don't allocate textures and images every frame)
22// TODO: Remove references to view size from shader
33// TODO: Make shader ignore metaballs that are outside a certain area of tolerance (eg ±100 from borders)
44
5+// TODO: Metaballs from texture to buffer
6+// TODO: Use async dispatch to free up main queue
7+// TODO: Refactor update into 2 calls: metalRendering() and textureToImage()
8+// TODO: Use two textures/images to alternate the 2 calls (like OpenGL double buffers)
9+
510// TODO: Make graphics have edges only according to graph (instead of being a complete graph)
611
712// TODO: Interpolate metaball colors
@@ -15,39 +20,58 @@ class MetalMetaballRenderer: MetaballRenderer {
1520
1621 typealias TargetView = UIImageView
1722
18− static let context = MTLContext()
23+ let targetView = TargetView()
24+ var previousFrame = CGRect.zero
25+
26+ let context = MTLContext()
27+ var renderingTexture: MTLTexture!
28+ var imageBuffer: UnsafeMutablePointer<Void>!
29+
30+ let dataSource: MetaballDataSource
31+
32+ required init(dataSource: MetaballDataSource) {
33+ self.dataSource = dataSource
34+ }
1935
20− static func updateTargetView(imageView: UIImageView, dataSource: MetaballDataSource) {
36+ func updateTargetView() {
2137 let metaballs = dataSource.metaballs
2238
39+ if previousFrame != targetView.frame {
40+ previousFrame = targetView.frame
41+ updateFrame()
42+ }
43+
44+ let width = Int(targetView.width)
45+ let height = Int(targetView.height)
46+
47+ if renderingTexture == nil {
48+ let textureDescriptor =
49+ MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.BGRA8Unorm, width: width, height: height, mipmapped: false)
50+
51+ renderingTexture = context.device.newTextureWithDescriptor(textureDescriptor)
52+ }
53+
2354 let kernelFunction: MTLFunction?
2455 let pipeline: MTLComputePipelineState
25− var internalTexture: MTLTexture! = nil
26−
2756 do {
57+ // Get long-lived objects
2858 kernelFunction = context.library.newFunctionWithName("drawMetaballs")
2959 pipeline = try context.device.newComputePipelineStateWithFunction(kernelFunction!)
3060
31− let width = Int(imageView.width)
32− let height = Int(imageView.height)
33−
34− let textureDescriptor =
35− MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.BGRA8Unorm, width: width, height: height, mipmapped: false)
36−
37− internalTexture = context.device.newTextureWithDescriptor(textureDescriptor)
61+ // Configure info for computing
3862 let threadGroupCounts = MTLSizeMake(8, 8, 1)
3963 let threadGroups = MTLSizeMake(width / threadGroupCounts.width,
4064 height / threadGroupCounts.height, 1)
4165
42− //
66+ // Create up-to-date metaball buffer
4367 let metaballInfoTexture = metaballTexture(metaballs)
4468
45− //
69+ // Send commands to metal and render
4670 let commandBuffer = context.commandQueue.commandBuffer()
4771
4872 let commandEncoder = commandBuffer.computeCommandEncoder()
4973 commandEncoder.setComputePipelineState(pipeline)
50− commandEncoder.setTexture(internalTexture, atIndex: 0)
74+ commandEncoder.setTexture(renderingTexture, atIndex: 0)
5175 commandEncoder.setTexture(metaballInfoTexture, atIndex: 1)
5276 commandEncoder.dispatchThreadgroups(threadGroups,
5377 threadsPerThreadgroup: threadGroupCounts)
@@ -60,29 +84,32 @@ class MetalMetaballRenderer: MetaballRenderer {
6084 print("Error!")
6185 }
6286
63− imageView.image = image(texture: internalTexture)
87+ // Transform new metal texture into image
88+ targetView.image = image(texture: renderingTexture)
6489 }
6590
66− static func image(texture texture: MTLTexture) -> UIImage {
91+ func image(texture texture: MTLTexture) -> UIImage {
92+ // Get image info
6793 let imageSize = CGSizeMake(CGFloat(texture.width),
6894 CGFloat(texture.height))
69−
7095 let width = Int(imageSize.width)
7196 let height = Int(imageSize.height)
72−
7397 let imageByteCount = width * height * 4
74− let imageBytes = malloc(imageByteCount)
7598 let bytesPerRow = width * 4
7699 let region = MTLRegionMake2D(0, 0, width, height)
77− texture.getBytes(imageBytes, bytesPerRow: bytesPerRow,
78− fromRegion: region, mipmapLevel: 0)
79100
80− let provider = CGDataProviderCreateWithData(nil, imageBytes,
81− imageByteCount) {
82− (_, data, _) -> Void in
83− free(UnsafeMutablePointer<Void>(data))
101+ // Allocate the buffer if needed
102+ if imageBuffer == nil {
103+ imageBuffer = malloc(imageByteCount)
84104 }
85105
106+ // Transfer texture info into image buffer
107+ texture.getBytes(imageBuffer, bytesPerRow: bytesPerRow,
108+ fromRegion: region, mipmapLevel: 0)
109+
110+ // Get info for UIImage
111+ let provider = CGDataProviderCreateWithData(nil, imageBuffer,
112+ imageByteCount, nil)
86113 let bitsPerComponent = 8
87114 let bitsperPixel = 32
88115 let colorSpaceRef = CGColorSpaceCreateDeviceRGB()
@@ -90,6 +117,8 @@ class MetalMetaballRenderer: MetaballRenderer {
90117 CGImageAlphaInfo.PremultipliedLast.rawValue |
91118 CGBitmapInfo.ByteOrder32Big.rawValue)
92119 let renderingIntent = CGColorRenderingIntent.RenderingIntentDefault
120+
121+ // Create UIImage from image buffer
93122 let imageRef = CGImageCreate(width, height,
94123 bitsPerComponent, bitsperPixel, bytesPerRow,
95124 colorSpaceRef, bitmapInfo, provider,
@@ -100,27 +129,37 @@ class MetalMetaballRenderer: MetaballRenderer {
100129 return image
101130 }
102131
103− static func metaballTexture(metaballs: [Metaball]) -> MTLTexture {
132+ func metaballTexture(metaballs: [Metaball]) -> MTLTexture {
133+ // Create Float array for buffer
104134 let floats: [Float] = metaballs.reduce([Float(metaballs.count)]) { (var array, metaball) -> [Float] in
105135 array.append(Float(metaball.midX))
106136 array.append(Float(metaball.midY))
107137 return array
108138 }
109− let size = floats.count
110139
140+ // Create texture
141+ let size = floats.count
111142 let textureDescriptor =
112143 MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(
113144 .R32Float, width: size, height: 1, mipmapped: false)
114−
115145 let texture = context.device.newTextureWithDescriptor(textureDescriptor)
116146
147+ // Copy array info into texture
117148 let region = MTLRegionMake2D(0, 0, size, 1)
118−
119149 texture.replaceRegion(region, mipmapLevel: 0, withBytes: floats,
120150 bytesPerRow: sizeof(Float) * size)
121151
122152 return texture
123153 }
124−
125−}
126154
155+ func updateFrame() {
156+ if imageBuffer != nil {
157+ free(imageBuffer)
158+ }
159+ imageBuffer = nil
160+ }
161+
162+ deinit {
163+ free(imageBuffer)
164+ }
165+}
PixelImage/ViewController.swift+7−4
@@ -8,6 +8,7 @@ class ViewController: UIViewController, MetaballDataSource {
88 typealias TargetView = Renderer.TargetView
99
1010 var metaballView: TargetView!
11+ var renderer: Renderer!
1112
1213 let width = 350
1314 let height = 600
@@ -20,6 +21,7 @@ class ViewController: UIViewController, MetaballDataSource {
2021 override func viewDidLoad() {
2122 super.viewDidLoad()
2223
24+ renderer = Renderer(dataSource: self)
2325
2426 let recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
2527 view.addGestureRecognizer(recognizer)
@@ -27,7 +29,8 @@ class ViewController: UIViewController, MetaballDataSource {
2729
2830 let border = 20
2931 let metaballViewFrame = CGRect(x: border/2, y: border/2, width: width, height: height)
30− metaballView = Renderer.createTargetView(frame: metaballViewFrame)
32+ metaballView = renderer.targetView
33+ metaballView.frame = metaballViewFrame
3134
3235 let bigView = UIView(frame: CGRect(x: 10, y: 70, width: width + border, height: height + border))
3336 bigView.backgroundColor = UIColor.redColor()
@@ -40,7 +43,7 @@ class ViewController: UIViewController, MetaballDataSource {
4043 }
4144
4245
43− Renderer.updateTargetView(metaballView, dataSource: self)
46+ renderer.updateTargetView()
4447 }
4548
4649 func handlePan(recognizer: UIPanGestureRecognizer) {
@@ -57,10 +60,10 @@ class ViewController: UIViewController, MetaballDataSource {
5760 selectedMetaball?.middle = location
5861
5962 if recognizer.state == .Changed && dynamicRendering {
60− Renderer.updateTargetView(metaballView, dataSource: self)
63+ renderer.updateTargetView()
6164 }
6265 if recognizer.state == .Ended {
63− Renderer.updateTargetView(metaballView, dataSource: self)
66+ renderer.updateTargetView()
6467 }
6568 }
6669}