git.iko.soy/metal-metaballs
on master help sign in
Merged release-optimization into master
59406eavinivendra
.gitignore+2
@@ -1 +1,3 @@
1.DS*
1.DS*
2+PixelImage.xcodeproj/xcuserdata/*
3+PixelImage.xcodeproj/project.xcworkspace/xcuserdata/*
PixelImage.xcodeproj/project.pbxproj+4−8
@@ -13,13 +13,12 @@
13 2426868F1C63B72A00D62456 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2426868E1C63B72A00D62456 /* Assets.xcassets */; };
13 2426868F1C63B72A00D62456 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2426868E1C63B72A00D62456 /* Assets.xcassets */; };
14 242686921C63B72A00D62456 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 242686901C63B72A00D62456 /* LaunchScreen.storyboard */; };
14 242686921C63B72A00D62456 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 242686901C63B72A00D62456 /* LaunchScreen.storyboard */; };
15 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */; };
15 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */; };
16− 24E1E7D91C67C9C900ECF1C4 /* MetaballRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7D81C67C9C900ECF1C4 /* MetaballRenderer.swift */; };
17− 24E1E7DB1C67CA3600ECF1C4 /* ImageMetaballRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7DA1C67CA3600ECF1C4 /* ImageMetaballRenderer.swift */; };
18 24E1E7E21C67D37F00ECF1C4 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */; };
16 24E1E7E21C67D37F00ECF1C4 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */; };
19 24E1E7E41C67D39300ECF1C4 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */; };
17 24E1E7E41C67D39300ECF1C4 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */; };
20 24E1E7EB1C67D70300ECF1C4 /* MetalMetaballRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */; };
18 24E1E7EB1C67D70300ECF1C4 /* MetalMetaballRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */; };
21 24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */; };
19 24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */; };
22 24E1E7F11C67D74300ECF1C4 /* shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */; };
20 24E1E7F11C67D74300ECF1C4 /* shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */; };
21+ 24ED21CD1C6C2397009C40FC /* MetaballDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */; };
23 24FFA86B1C6BA038005ACD60 /* ApproximationOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84C1C6BA038005ACD60 /* ApproximationOperator.swift */; };
22 24FFA86B1C6BA038005ACD60 /* ApproximationOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84C1C6BA038005ACD60 /* ApproximationOperator.swift */; };
24 24FFA86C1C6BA038005ACD60 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84D1C6BA038005ACD60 /* Collections.swift */; };
23 24FFA86C1C6BA038005ACD60 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84D1C6BA038005ACD60 /* Collections.swift */; };
25 24FFA86D1C6BA038005ACD60 /* ComparatorChains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84E1C6BA038005ACD60 /* ComparatorChains.swift */; };
24 24FFA86D1C6BA038005ACD60 /* ComparatorChains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84E1C6BA038005ACD60 /* ComparatorChains.swift */; };
@@ -59,13 +58,12 @@
59 242686911C63B72A00D62456 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
58 242686911C63B72A00D62456 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
60 242686931C63B72A00D62456 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
59 242686931C63B72A00D62456 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
61 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = "<group>"; };
60 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = "<group>"; };
62− 24E1E7D81C67C9C900ECF1C4 /* MetaballRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetaballRenderer.swift; sourceTree = "<group>"; };
63− 24E1E7DA1C67CA3600ECF1C4 /* ImageMetaballRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageMetaballRenderer.swift; sourceTree = "<group>"; };
64 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
61 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
65 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; };
62 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; };
66 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalMetaballRenderer.swift; sourceTree = "<group>"; };
63 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalMetaballRenderer.swift; sourceTree = "<group>"; };
67 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metal.swift; sourceTree = "<group>"; };
64 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metal.swift; sourceTree = "<group>"; };
68 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = shaders.metal; sourceTree = "<group>"; };
65 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = shaders.metal; sourceTree = "<group>"; };
66+ 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetaballDataSource.swift; sourceTree = "<group>"; };
69 24FFA84C1C6BA038005ACD60 /* ApproximationOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApproximationOperator.swift; sourceTree = "<group>"; };
67 24FFA84C1C6BA038005ACD60 /* ApproximationOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApproximationOperator.swift; sourceTree = "<group>"; };
70 24FFA84D1C6BA038005ACD60 /* Collections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collections.swift; sourceTree = "<group>"; };
68 24FFA84D1C6BA038005ACD60 /* Collections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collections.swift; sourceTree = "<group>"; };
71 24FFA84E1C6BA038005ACD60 /* ComparatorChains.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComparatorChains.swift; sourceTree = "<group>"; };
69 24FFA84E1C6BA038005ACD60 /* ComparatorChains.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComparatorChains.swift; sourceTree = "<group>"; };
@@ -134,8 +132,7 @@
134 242686871C63B72A00D62456 /* AppDelegate.swift */,
132 242686871C63B72A00D62456 /* AppDelegate.swift */,
135 242686891C63B72A00D62456 /* ViewController.swift */,
133 242686891C63B72A00D62456 /* ViewController.swift */,
136 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */,
134 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */,
137− 24E1E7D81C67C9C900ECF1C4 /* MetaballRenderer.swift */,
135+ 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */,
138− 24E1E7DA1C67CA3600ECF1C4 /* ImageMetaballRenderer.swift */,
139 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */,
136 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */,
140 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */,
137 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */,
141 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */,
138 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */,
@@ -289,13 +286,13 @@
289 24FFA8861C6BA038005ACD60 /* TestCommons.swift in Sources */,
286 24FFA8861C6BA038005ACD60 /* TestCommons.swift in Sources */,
290 24FFA8741C6BA038005ACD60 /* SignOperator.swift in Sources */,
287 24FFA8741C6BA038005ACD60 /* SignOperator.swift in Sources */,
291 24FFA8821C6BA038005ACD60 /* ScanReduce.swift in Sources */,
288 24FFA8821C6BA038005ACD60 /* ScanReduce.swift in Sources */,
289+ 24ED21CD1C6C2397009C40FC /* MetaballDataSource.swift in Sources */,
292 24FFA8831C6BA038005ACD60 /* Slicing.swift in Sources */,
290 24FFA8831C6BA038005ACD60 /* Slicing.swift in Sources */,
293 24FFA8721C6BA038005ACD60 /* Sequence.swift in Sources */,
291 24FFA8721C6BA038005ACD60 /* Sequence.swift in Sources */,
294 24FFA87F1C6BA038005ACD60 /* Iterate.swift in Sources */,
292 24FFA87F1C6BA038005ACD60 /* Iterate.swift in Sources */,
295 24FFA8761C6BA038005ACD60 /* Categorise.swift in Sources */,
293 24FFA8761C6BA038005ACD60 /* Categorise.swift in Sources */,
296 24FFA86D1C6BA038005ACD60 /* ComparatorChains.swift in Sources */,
294 24FFA86D1C6BA038005ACD60 /* ComparatorChains.swift in Sources */,
297 24FFA8711C6BA038005ACD60 /* PowerOperator.swift in Sources */,
295 24FFA8711C6BA038005ACD60 /* PowerOperator.swift in Sources */,
298− 24E1E7D91C67C9C900ECF1C4 /* MetaballRenderer.swift in Sources */,
299 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */,
296 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */,
300 24FFA8851C6BA038005ACD60 /* Zip.swift in Sources */,
297 24FFA8851C6BA038005ACD60 /* Zip.swift in Sources */,
301 24FFA8791C6BA038005ACD60 /* Cycle.swift in Sources */,
298 24FFA8791C6BA038005ACD60 /* Cycle.swift in Sources */,
@@ -304,7 +301,6 @@
304 24FFA87D1C6BA038005ACD60 /* Hopping.swift in Sources */,
301 24FFA87D1C6BA038005ACD60 /* Hopping.swift in Sources */,
305 24E1E7EB1C67D70300ECF1C4 /* MetalMetaballRenderer.swift in Sources */,
302 24E1E7EB1C67D70300ECF1C4 /* MetalMetaballRenderer.swift in Sources */,
306 24FFA87A1C6BA038005ACD60 /* Enumerate.swift in Sources */,
303 24FFA87A1C6BA038005ACD60 /* Enumerate.swift in Sources */,
307− 24E1E7DB1C67CA3600ECF1C4 /* ImageMetaballRenderer.swift in Sources */,
308 242686881C63B72A00D62456 /* AppDelegate.swift in Sources */,
304 242686881C63B72A00D62456 /* AppDelegate.swift in Sources */,
309 24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */,
305 24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */,
310 24FFA87C1C6BA038005ACD60 /* FlatMap.swift in Sources */,
306 24FFA87C1C6BA038005ACD60 /* FlatMap.swift in Sources */,
PixelImage.xcodeproj/project.xcworkspace/xcuserdata/vini.xcuserdatad/UserInterfaceState.xcuserstatedeleted

Binary file not shown.

PixelImage.xcodeproj/xcuserdata/vini.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlistdeleted−23
@@ -1,23 +0,0 @@
1−<?xml version="1.0" encoding="UTF-8"?>
2−<Bucket
3− type = "1"
4− version = "2.0">
5− <Breakpoints>
6− <BreakpointProxy
7− BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
8− <BreakpointContent
9− shouldBeEnabled = "Yes"
10− ignoreCount = "0"
11− continueAfterRunningActions = "No"
12− filePath = "PixelImage/MetalMetaballRenderer.swift"
13− timestampString = "476814708.217603"
14− startingColumnNumber = "9223372036854775807"
15− endingColumnNumber = "9223372036854775807"
16− startingLineNumber = "62"
17− endingLineNumber = "62"
18− landmarkName = "updateTargetView(_:dataSource:)"
19− landmarkType = "5">
20− </BreakpointContent>
21− </BreakpointProxy>
22− </Breakpoints>
23−</Bucket>
PixelImage/ImageMetaballRenderer.swiftdeleted−101
@@ -1,101 +0,0 @@
1−
2−import UIKit
3−
4−class ImageMetaballRenderer: MetaballRenderer {
5− typealias TargetView = UIImageView
6−
7− static func updateTargetView(imageView: TargetView, dataSource: MetaballDataSource) {
8− let width = Int(imageView.width)
9− let height = Int(imageView.height)
10− let metaballs = dataSource.metaballs
11−
12− let bytesPerPixel = 4;
13− let bytesPerRow = bytesPerPixel * width;
14− let bitsPerComponent = 8;
15−
16− var matrix = PixelMatrix(width: width, height: height)
17− for i in 0..<width {
18− for j in 0..<height {
19− let pixelPosition = CGPoint(x: i, y: j)
20−
21− var sum: CGFloat = 0
22−
23− for combo in metaballs.combos() {
24− let metaball1 = combo[0].middle
25− let metaball2 = combo[1].middle
26−
27− let metaball1Vector = metaball1 - pixelPosition
28− let metaball2Vector = metaball2 - pixelPosition
29−
30− let direction1 = metaball1Vector.normalized
31− let direction2 = metaball2Vector.normalized
32−
33− let cos = direction1 * direction2
34−
35− let value1 = 255000 / (metaball1Vector * metaball1Vector + 1)
36− let value2 = 255000 / (metaball2Vector * metaball2Vector + 1)
37−
38− let v = gradient(value1 + value2)
39− let link = ((1 - cos) * 0.5) ^ 50
40−
41− let weightedLink = 128 * CGFloat(link)
42− let weightedValue = 0.6 * CGFloat(v)
43−
44− sum += weightedValue + weightedLink
45− }
46−
47− let result = threshold(sum)
48−
49− matrix.set(x: i, y: j, r:0, g:result/2, b:result)
50− }
51− }
52−
53− let buffer: UnsafeMutablePointer<Void> = UnsafeMutablePointer(matrix.pixels)
54−
55− let colorSpace = CGColorSpaceCreateDeviceRGB()
56− let context = CGBitmapContextCreate(buffer, width, height, bitsPerComponent, bytesPerRow, colorSpace, CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Big.rawValue);
57−
58− let cgImage = CGBitmapContextCreateImage(context)
59− let image = UIImage(CGImage: cgImage!)
60− imageView.image = image
61− }
62−}
63−
64−struct PixelMatrix {
65− var pixels: [UInt8]
66−
67− let width: Int
68− let height: Int
69−
70− init(width: Int, height: Int) {
71− self.width = width
72− self.height = height
73− pixels = [UInt8](count: (4 * width * height), repeatedValue: 255)
74− }
75−
76− mutating func set(x x: Int, y: Int, r: UInt8, g: UInt8, b: UInt8) {
77− let index = 4 * (y * width + x)
78− pixels[index ] = r
79− pixels[index + 1] = g
80− pixels[index + 2] = b
81− }
82−
83− mutating func set(x x: Int, y: Int, white: UInt8) {
84− let index = 4 * (y * width + x)
85− pixels[index ] = white
86− pixels[index + 1] = white
87− pixels[index + 2] = white
88− }
89−}
90−
91−func threshold(value: CGFloat) -> UInt8 {
92− return value < 128 ? 0 : 255
93−}
94−
95−func gradient(value: CGFloat) -> UInt8 {
96− return UInt8(min(max(value, 0), CGFloat(255)))
97−}
98−
99−func thresholdf(value value: CGFloat, cap: CGFloat = 0.5) -> CGFloat {
100− return value < cap ? 0 : 1.0
101−}
PixelImage/MetaballDataSource.swiftnew file+6
@@ -0,0 +1,6 @@
1+
2+import Metal
3+
4+protocol MetaballDataSource {
5+ var metaballs: [Metaball] { get }
6+}
PixelImage/MetaballRenderer.swiftdeleted−18
@@ -1,18 +0,0 @@
1−
2−import UIKit
3−
4−protocol MetaballDataSource {
5− var metaballs: [Metaball] { get }
6−}
7−
8−protocol MetaballRenderer {
9− typealias TargetView: UIView
10−
11− static func updateTargetView(targetView: TargetView, dataSource: MetaballDataSource)
12−}
13−
14−extension MetaballRenderer {
15− static func createTargetView(frame frame: CGRect) -> TargetView {
16− return TargetView(frame: frame)
17− }
18−}
PixelImage/Metal.swift+30
@@ -1,4 +1,5 @@
1import Metal
1import Metal
2+import UIKit
2
3
3class MTLContext {
4class MTLContext {
4 let device: MTLDevice
5 let device: MTLDevice
@@ -11,3 +12,32 @@ class MTLContext {
11 self.commandQueue = device.newCommandQueue()
12 self.commandQueue = device.newCommandQueue()
12 }
13 }
13}
14}
15+
16+class MTLComputeContext {
17+ var texture: MTLTexture
18+ var imageBuffer: UnsafeMutablePointer<Void>
19+
20+ init(size: CGSize, texture: MTLTexture) {
21+ let width = Int(size.width)
22+ let height = Int(size.height)
23+ let imageByteCount = width * height * 4
24+ imageBuffer = malloc(imageByteCount)
25+
26+ self.texture = texture
27+ }
28+
29+ func updateToSize(size: CGSize, texture: MTLTexture) {
30+ free(imageBuffer)
31+
32+ let width = Int(size.width)
33+ let height = Int(size.height)
34+ let imageByteCount = width * height * 4
35+ imageBuffer = malloc(imageByteCount)
36+
37+ self.texture = texture
38+ }
39+
40+ deinit {
41+ free(imageBuffer)
42+ }
43+}
PixelImage/MetalMetaballRenderer.swift+123−54
@@ -1,7 +1,3 @@
1−// TODO: Render metaballs on layer/view instead of image
2−// TODO: Remove references to view size from shader
3−// TODO: Make shader ignore metaballs that are outside a certain area of tolerance (eg ±100 from borders)
4−
5// TODO: Make graphics have edges only according to graph (instead of being a complete graph)
1// TODO: Make graphics have edges only according to graph (instead of being a complete graph)
6
2
7// TODO: Interpolate metaball colors
3// TODO: Interpolate metaball colors
@@ -11,44 +7,113 @@
11import Metal
7import Metal
12import UIKit
8import UIKit
13
9
10+var i = 0
11+
14−class MetalMetaballRenderer: MetaballRenderer {
12+class MetalMetaballRenderer {
15
13
16 typealias TargetView = UIImageView
14 typealias TargetView = UIImageView
17
15
16+ let targetView: TargetView
17+
18− static let context = MTLContext()
18+ let context = MTLContext()
19+ var activeComputeContext: MTLComputeContext
20+ var idleComputeContext: MTLComputeContext
21+
22+ let dataSource: MetaballDataSource
23+
24+ let semaphore = dispatch_semaphore_create(2)
25+
26+ enum RendererState {
27+ case Idle
28+ case Running
29+ case Ending
30+ }
31+
32+ var state: RendererState = .Idle {
33+ willSet {
34+ if state == .Idle {
35+ if newValue == .Running {
36+ updateTargetView()
37+ }
38+ }
39+ }
40+ }
41+
42+ required init(dataSource: MetaballDataSource, frame: CGRect) {
43+ self.dataSource = dataSource
44+
45+ targetView = TargetView(frame: frame)
46+
47+ let width = Int(frame.width)
48+ let height = Int(frame.height)
49+ let textureDescriptor =
50+ MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.BGRA8Unorm, width: width, height: height, mipmapped: false)
51+
52+ let texture1 = context.device.newTextureWithDescriptor(textureDescriptor)
53+ activeComputeContext = MTLComputeContext(size: targetView.size, texture: texture1)
54+ let texture2 = context.device.newTextureWithDescriptor(textureDescriptor)
55+ idleComputeContext = MTLComputeContext(size: targetView.size, texture: texture2)
56+ }
57+
58+ func updateTargetView() {
59+
60+ let timeout = dispatch_time(DISPATCH_TIME_NOW, 1000000000)
61+ dispatch_semaphore_wait(semaphore, timeout)
62+
63+ print("Update \(i++)!")
64+
65+ state = .Ending
66+
67+ dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) { () -> Void in
68+
69+ let computeContext = self.activeComputeContext
70+ swap(&self.activeComputeContext, &self.idleComputeContext)
71+
72+ // Render graphics to metal texture
73+ self.renderToContext(computeContext)
19
74
20− static func updateTargetView(imageView: UIImageView, dataSource: MetaballDataSource) {
75+ if self.state != .Running {
21− let metaballs = dataSource.metaballs
76+ self.state = .Idle
77+ } else {
78+ self.updateTargetView()
79+ }
80+
81+ // Transform metal texture into image
82+ let uiimage = self.createImageFromContext(computeContext)
83+
84+ dispatch_semaphore_signal(self.semaphore)
85+
86+ dispatch_async(dispatch_get_main_queue(), { () -> Void in
87+ // Send image to view
88+ self.targetView.image = uiimage
89+ })
90+ }
91+ }
92+
93+ func renderToContext(computeContext: MTLComputeContext) {
94+ let width = Int(targetView.width)
95+ let height = Int(targetView.height)
22
96
23 let kernelFunction: MTLFunction?
97 let kernelFunction: MTLFunction?
24 let pipeline: MTLComputePipelineState
98 let pipeline: MTLComputePipelineState
25− var internalTexture: MTLTexture! = nil
26−
27 do {
99 do {
100+ // Get long-lived objects
28 kernelFunction = context.library.newFunctionWithName("drawMetaballs")
101 kernelFunction = context.library.newFunctionWithName("drawMetaballs")
29 pipeline = try context.device.newComputePipelineStateWithFunction(kernelFunction!)
102 pipeline = try context.device.newComputePipelineStateWithFunction(kernelFunction!)
30
103
31− let width = Int(imageView.width)
104+ // Configure info for computing
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)
38 let threadGroupCounts = MTLSizeMake(8, 8, 1)
105 let threadGroupCounts = MTLSizeMake(8, 8, 1)
39 let threadGroups = MTLSizeMake(width / threadGroupCounts.width,
106 let threadGroups = MTLSizeMake(width / threadGroupCounts.width,
40 height / threadGroupCounts.height, 1)
107 height / threadGroupCounts.height, 1)
41
108
42− //
109+ // Send commands to metal and render
43− let metaballInfoTexture = metaballTexture(metaballs)
44−
45− //
46 let commandBuffer = context.commandQueue.commandBuffer()
110 let commandBuffer = context.commandQueue.commandBuffer()
47
111
48 let commandEncoder = commandBuffer.computeCommandEncoder()
112 let commandEncoder = commandBuffer.computeCommandEncoder()
49 commandEncoder.setComputePipelineState(pipeline)
113 commandEncoder.setComputePipelineState(pipeline)
50− commandEncoder.setTexture(internalTexture, atIndex: 0)
114+ commandEncoder.setTexture(computeContext.texture, atIndex: 0)
51− commandEncoder.setTexture(metaballInfoTexture, atIndex: 1)
115+ let metaballInfoBuffer = metaballBuffer()
116+ commandEncoder.setBuffer(metaballInfoBuffer, offset: 0, atIndex: 0)
52 commandEncoder.dispatchThreadgroups(threadGroups,
117 commandEncoder.dispatchThreadgroups(threadGroups,
53 threadsPerThreadgroup: threadGroupCounts)
118 threadsPerThreadgroup: threadGroupCounts)
54 commandEncoder.endEncoding()
119 commandEncoder.endEncoding()
@@ -59,30 +124,32 @@ class MetalMetaballRenderer: MetaballRenderer {
59 } catch _ {
124 } catch _ {
60 print("Error!")
125 print("Error!")
61 }
126 }
62−
63− imageView.image = image(texture: internalTexture)
64 }
127 }
65
128
66− static func image(texture texture: MTLTexture) -> UIImage {
129+ func createImageFromContext(computeContext: MTLComputeContext) -> UIImage {
130+ let texture = computeContext.texture
131+
132+ // Get image info
67 let imageSize = CGSizeMake(CGFloat(texture.width),
133 let imageSize = CGSizeMake(CGFloat(texture.width),
68 CGFloat(texture.height))
134 CGFloat(texture.height))
69−
70 let width = Int(imageSize.width)
135 let width = Int(imageSize.width)
71 let height = Int(imageSize.height)
136 let height = Int(imageSize.height)
72−
73 let imageByteCount = width * height * 4
137 let imageByteCount = width * height * 4
74− let imageBytes = malloc(imageByteCount)
75 let bytesPerRow = width * 4
138 let bytesPerRow = width * 4
76 let region = MTLRegionMake2D(0, 0, width, height)
139 let region = MTLRegionMake2D(0, 0, width, height)
77− texture.getBytes(imageBytes, bytesPerRow: bytesPerRow,
78− fromRegion: region, mipmapLevel: 0)
79
140
80− let provider = CGDataProviderCreateWithData(nil, imageBytes,
141+ // Allocate the buffer if needed
142+ if computeContext.imageBuffer == nil {
81− imageByteCount) {
143+ computeContext.imageBuffer = malloc(imageByteCount)
82− (_, data, _) -> Void in
83− free(UnsafeMutablePointer<Void>(data))
84 }
144 }
85
145
146+ // Transfer texture info into image buffer
147+ texture.getBytes(computeContext.imageBuffer, bytesPerRow: bytesPerRow,
148+ fromRegion: region, mipmapLevel: 0)
149+
150+ // Get info for UIImage
151+ let provider = CGDataProviderCreateWithData(nil, computeContext.imageBuffer,
152+ imageByteCount, nil)
86 let bitsPerComponent = 8
153 let bitsPerComponent = 8
87 let bitsperPixel = 32
154 let bitsperPixel = 32
88 let colorSpaceRef = CGColorSpaceCreateDeviceRGB()
155 let colorSpaceRef = CGColorSpaceCreateDeviceRGB()
@@ -90,37 +157,39 @@ class MetalMetaballRenderer: MetaballRenderer {
90 CGImageAlphaInfo.PremultipliedLast.rawValue |
157 CGImageAlphaInfo.PremultipliedLast.rawValue |
91 CGBitmapInfo.ByteOrder32Big.rawValue)
158 CGBitmapInfo.ByteOrder32Big.rawValue)
92 let renderingIntent = CGColorRenderingIntent.RenderingIntentDefault
159 let renderingIntent = CGColorRenderingIntent.RenderingIntentDefault
160+
161+ // Create UIImage from image buffer
93 let imageRef = CGImageCreate(width, height,
162 let imageRef = CGImageCreate(width, height,
94 bitsPerComponent, bitsperPixel, bytesPerRow,
163 bitsPerComponent, bitsperPixel, bytesPerRow,
95 colorSpaceRef, bitmapInfo, provider,
164 colorSpaceRef, bitmapInfo, provider,
96 nil, false, renderingIntent)
165 nil, false, renderingIntent)
97 let image = UIImage(CGImage: imageRef!, scale: 0.0,
166 let image = UIImage(CGImage: imageRef!, scale: 0.0,
98− orientation: UIImageOrientation.DownMirrored)
167+ orientation: UIImageOrientation.Up)
99
168
100 return image
169 return image
101 }
170 }
102
171
103− static func metaballTexture(metaballs: [Metaball]) -> MTLTexture {
172+ func metaballBuffer() -> MTLBuffer {
173+ // Create Float array for buffer
174+ // Exclude metaballs far from the view's bounds
175+ let border: CGFloat = 100
176+ let metaballs = self.dataSource.metaballs
104− let floats: [Float] = metaballs.reduce([Float(metaballs.count)]) { (var array, metaball) -> [Float] in
177+ var floats: [Float] = metaballs.reduce([Float(0)]) { (var array, metaball) -> [Float] in
105− array.append(Float(metaball.midX))
178+ let x = CGFloat(metaball.midX)
106− array.append(Float(metaball.midY))
179+ let y = CGFloat(metaball.midY)
180+ if -border < x ≤ targetView.width + border &&
181+ -border < y ≤ targetView.height + border {
182+ array.append(Float(x))
183+ array.append(Float(y))
184+ }
107 return array
185 return array
108 }
186 }
187+ // Add array size to start of array so metal knows how far to go
109− let size = floats.count
188+ floats[0] = Float(floats.count) / 2
110
189
111− let textureDescriptor =
190+ // Create buffer
112− MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(
191+ let buffer = context.device.newBufferWithBytes(floats, length: floats.count * sizeof(Float), options: .StorageModeShared)
113− .R32Float, width: size, height: 1, mipmapped: false)
114−
115− let texture = context.device.newTextureWithDescriptor(textureDescriptor)
116−
117− let region = MTLRegionMake2D(0, 0, size, 1)
118
192
119− texture.replaceRegion(region, mipmapLevel: 0, withBytes: floats,
120− bytesPerRow: sizeof(Float) * size)
121−
122− return texture
193+ return buffer
123 }
194 }
124−
125}
195}
126−
PixelImage/UIKit.swift+2
@@ -44,6 +44,8 @@ extension CGFloat: DoubleValuable {
44 }
44 }
45}
45}
46
46
47+extension CGFloat: Numeric {}
48+
47extension CGRect {
49extension CGRect {
48 var x: CGFloat {
50 var x: CGFloat {
49 get { return origin.x }
51 get { return origin.x }
PixelImage/ViewController.swift+18−21
@@ -3,11 +3,8 @@ import UIKit
3
3
4class ViewController: UIViewController, MetaballDataSource {
4class ViewController: UIViewController, MetaballDataSource {
5
5
6− let dynamicRendering = true
7− typealias Renderer = MetalMetaballRenderer // ImageMetaballRenderer // MetalMetaballRenderer
8− typealias TargetView = Renderer.TargetView
9−
10− var metaballView: TargetView!
6+ var metaballView: UIImageView!
7+ var renderer: MetalMetaballRenderer!
11
8
12 let width = 350
9 let width = 350
13 let height = 600
10 let height = 600
@@ -17,50 +14,50 @@ class ViewController: UIViewController, MetaballDataSource {
17 }
14 }
18 var previousLocation: CGPoint!
15 var previousLocation: CGPoint!
19
16
17+ var selectedMetaball: Metaball?
18+
20 override func viewDidLoad() {
19 override func viewDidLoad() {
21 super.viewDidLoad()
20 super.viewDidLoad()
22
21
23−
24 let recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
22 let recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
25 view.addGestureRecognizer(recognizer)
23 view.addGestureRecognizer(recognizer)
26
24
27−
28 let border = 20
25 let border = 20
29 let metaballViewFrame = CGRect(x: border/2, y: border/2, width: width, height: height)
26 let metaballViewFrame = CGRect(x: border/2, y: border/2, width: width, height: height)
30− metaballView = Renderer.createTargetView(frame: metaballViewFrame)
27+ renderer = MetalMetaballRenderer(dataSource: self, frame: metaballViewFrame)
28+ metaballView = renderer.targetView
31
29
32 let bigView = UIView(frame: CGRect(x: 10, y: 70, width: width + border, height: height + border))
30 let bigView = UIView(frame: CGRect(x: 10, y: 70, width: width + border, height: height + border))
33 bigView.backgroundColor = UIColor.redColor()
31 bigView.backgroundColor = UIColor.redColor()
34 bigView.addSubview(metaballView)
32 bigView.addSubview(metaballView)
35 view.addSubview(bigView)
33 view.addSubview(bigView)
36
34
37−
38 for metaball in metaballs {
35 for metaball in metaballs {
39 metaballView.addSubview(metaball)
36 metaballView.addSubview(metaball)
40 }
37 }
41
38
42−
39+ renderer.state = .Running
43− Renderer.updateTargetView(metaballView, dataSource: self)
44 }
40 }
45
41
46 func handlePan(recognizer: UIPanGestureRecognizer) {
42 func handlePan(recognizer: UIPanGestureRecognizer) {
47 let location = recognizer.locationInView(metaballView)
43 let location = recognizer.locationInView(metaballView)
48
44
49− var selectedMetaball: UIView?
45+ if selectedMetaball == nil {
50−
51− for metaball in metaballs where
46+ for metaball in metaballs where
52− abs(metaball.midX - location.x) < 50 && abs(metaball.midY - location.y) < 50 {
47+ abs(metaball.midX - location.x) < 50 && abs(metaball.midY - location.y) < 50 {
53− selectedMetaball = metaball
48+ selectedMetaball = metaball
54− break
49+ break
50+ }
55 }
51 }
56
52
53+ guard selectedMetaball != nil else { return }
54+
57 selectedMetaball?.middle = location
55 selectedMetaball?.middle = location
58
56
59− if recognizer.state == .Changed && dynamicRendering {
57+ renderer.state = .Running
60− Renderer.updateTargetView(metaballView, dataSource: self)
58+
61− }
62 if recognizer.state == .Ended {
59 if recognizer.state == .Ended {
63− Renderer.updateTargetView(metaballView, dataSource: self)
60+ selectedMetaball = nil
64 }
61 }
65 }
62 }
66}
63}
PixelImage/shaders.metal+7−8
@@ -5,22 +5,21 @@ using namespace metal;
5
5
6kernel void
6kernel void
7 drawMetaballs(texture2d<float, access::write> outTexture[[texture(0)]],
7 drawMetaballs(texture2d<float, access::write> outTexture[[texture(0)]],
8− texture2d<float, access::read>
8+ constant float *metaballBuffer [[buffer(0)]],
9− metaballTexture[[texture(1)]],
10 uint2 gid[[thread_position_in_grid]]) {
9 uint2 gid[[thread_position_in_grid]]) {
11
10
12− char numberOfMetaballs = metaballTexture.read(0)[0];
11+ char numberOfMetaballs = metaballBuffer[0];
13 char metaballArraySize = numberOfMetaballs * 2 + 1;
12 char metaballArraySize = numberOfMetaballs * 2 + 1;
14
13
15 float sum = 0;
14 float sum = 0;
16
15
17 for (char i = 1; i < metaballArraySize; i += 2) {
16 for (char i = 1; i < metaballArraySize; i += 2) {
18 for (char j = i + 2; j < metaballArraySize; j += 2) {
17 for (char j = i + 2; j < metaballArraySize; j += 2) {
19− float2 metaball1 = float2(metaballTexture.read(i)[0],
18+ float2 metaball1 = float2(metaballBuffer[i],
20− 600 - metaballTexture.read(i + 1)[0]);
19+ metaballBuffer[i + 1]);
21
20
22− float2 metaball2 = float2(metaballTexture.read(j)[0],
21+ float2 metaball2 = float2(metaballBuffer[j],
23− 600 - metaballTexture.read(j + 1)[0]);
22+ metaballBuffer[j + 1]);
24
23
25 float2 metaball1Vector
24 float2 metaball1Vector
26 = float2(metaball1.x - gid.x, metaball1.y - gid.y);
25 = float2(metaball1.x - gid.x, metaball1.y - gid.y);
@@ -45,7 +44,7 @@ kernel void
45 }
44 }
46 }
45 }
47
46
48− float result = sum;
47+ float result = mix(0.0, 1.0, step(0.4, sum));
49
48
50 outTexture.write(float4(result, result / 2, 0, 1), gid);
49 outTexture.write(float4(result, result / 2, 0, 1), gid);
51}
50}