├── Screenshot.png
├── Wireframe_Screenshot.png
├── ios_metal_bezier_renderer.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── LICENSE
├── metal_bezier
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── ViewController.swift
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── AppDelegate.swift
├── bezier_shaders.metal
├── MetalBezierView.swift
└── PageAlignedArray.swift
└── README.md
/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rijn/ios_metal_bezier_renderer/master/Screenshot.png
--------------------------------------------------------------------------------
/Wireframe_Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rijn/ios_metal_bezier_renderer/master/Wireframe_Screenshot.png
--------------------------------------------------------------------------------
/ios_metal_bezier_renderer.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/metal_bezier/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/metal_bezier/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/metal_bezier/ViewController.swift:
--------------------------------------------------------------------------------
1 | //MIT License
2 | //
3 | //Copyright (c) 2016
4 | //
5 | //Permission is hereby granted, free of charge, to any person obtaining a copy
6 | //of this software and associated documentation files (the "Software"), to deal
7 | //in the Software without restriction, including without limitation the rights
8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | //copies of the Software, and to permit persons to whom the Software is
10 | //furnished to do so, subject to the following conditions:
11 | //
12 | //The above copyright notice and this permission notice shall be included in all
13 | //copies or substantial portions of the Software.
14 | //
15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | //SOFTWARE.
22 | import UIKit
23 | import MetalKit
24 |
25 | class ViewController: UIViewController {
26 |
27 | @IBOutlet var metalView : MTKView?
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 | // Do any additional setup after loading the view, typically from a nib.
32 | }
33 |
34 | override func didReceiveMemoryWarning() {
35 | super.didReceiveMemoryWarning()
36 | // Dispose of any resources that can be recreated.
37 | }
38 |
39 |
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/metal_bezier/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/metal_bezier/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //MIT License
2 | //
3 | //Copyright (c) 2016
4 | //
5 | //Permission is hereby granted, free of charge, to any person obtaining a copy
6 | //of this software and associated documentation files (the "Software"), to deal
7 | //in the Software without restriction, including without limitation the rights
8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | //copies of the Software, and to permit persons to whom the Software is
10 | //furnished to do so, subject to the following conditions:
11 | //
12 | //The above copyright notice and this permission notice shall be included in all
13 | //copies or substantial portions of the Software.
14 | //
15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | //SOFTWARE.
22 |
23 | import UIKit
24 |
25 | @UIApplicationMain
26 | class AppDelegate: UIResponder, UIApplicationDelegate {
27 |
28 | var window: UIWindow?
29 |
30 |
31 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
32 | // Override point for customization after application launch.
33 | return true
34 | }
35 |
36 | func applicationWillResignActive(_ application: UIApplication) {
37 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
38 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
39 | }
40 |
41 | func applicationDidEnterBackground(_ application: UIApplication) {
42 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
43 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
44 | }
45 |
46 | func applicationWillEnterForeground(_ application: UIApplication) {
47 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
48 | }
49 |
50 | func applicationDidBecomeActive(_ application: UIApplication) {
51 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
52 | }
53 |
54 | func applicationWillTerminate(_ application: UIApplication) {
55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
56 | }
57 |
58 |
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/metal_bezier/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # High-performance Bezier Curves in the GPU with Metal
2 | Lately I've been working on a project to achieve high-performance 2D graphics on iOS using Metal. It's interesting in that tasks that used to be supported by the fixed hardware in the older graphics processors have, in recent years, become a bit more challenging and less of a natural fit for today's modern GPU hardware.
3 |
4 | In this project I spent some time playing with rendering Bezier curves completely in the GPU and measuring the kind of performance that can be achieved. Other OpenGL implementations I was able to find online appeared to be cheating: They were actually calculating the vertices on the CPU and feeding the calculated data to the GPU, so all the GPU was doing was actually drawing the triangles to the screen. This is surprising given that the GPU appears to be very well suited to perform these kinds of calculations. Here is what the output of my test program looks like:
5 |
6 | 
7 |
8 | In this implementation the Bezier curve is fully calculated in the vertex shader, which achieves very high performance. The test program renders several thousand Bezier curves animating at 60FPS with perfect smoothness (and with 4x MSAA).
9 |
10 | ## Visualizing the triangles
11 | As with any GPU-based rendering, we use triangles to actually render our curves, which significantly complicates the vertex shader and adds cost. We could use simple lines and just have each vertex shader calculate a single point, but then all we'd get is a fixed-width curve. Instead, we compute triangles out of the curve and render those, which allows us to choose our thickness. Depending on the length of the curve and on the screen resolution, it appears that a couple of hundred triangles tend to be sufficient to achieve a nice smooth curve on an iPad's screen.
12 |
13 | Here is a wireframe image showing a single curve, to illustrate how the triangles are used to express the curve:
14 |
15 | 
16 |
17 | ## A note about performance
18 | While the code does perform well enough for most application, the performance is actually a bit unimpressive. For example, in terms of triangles/sec, the iPad Pro appears to be achieving around 2000 curves consisting of 200 triangles each at around 40FPS (around 25ms/frame). That equates a roughly 16M triangles/sec fillrate, which is **significantly** below
19 | the expected performance for that device.
20 |
21 | It is possible that the vertex shader computational load is the bottleneck, but Instruments and the GPU profiler inside Xcode are telling a different story, and are indicating that most of the time is being spent in the fragment shader (which in this implementation should be doing absolutely nothing).
22 |
23 | Another interesting data point is the fact that while increasing the number of curves dramatically increases the per-frame GPU render time, merely increasing the number of triangles *per curve* has a much smaller impact on overall performance.
24 |
25 | For example, on the same iPad Pro, doubling the number of triangles-per-curve from 200 to 400 only increases the per-frame time to around 31ms. That's a 24% increase after we basically doubled the GPU's workload!
26 |
27 | By way of comparison, if we double the number of curves instead (keeping the per-curve triangle count at 200), performance drops to around 47ms per frame.
28 |
--------------------------------------------------------------------------------
/metal_bezier/bezier_shaders.metal:
--------------------------------------------------------------------------------
1 | //MIT License
2 | //
3 | //Copyright (c) 2016
4 | //
5 | //Permission is hereby granted, free of charge, to any person obtaining a copy
6 | //of this software and associated documentation files (the "Software"), to deal
7 | //in the Software without restriction, including without limitation the rights
8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | //copies of the Software, and to permit persons to whom the Software is
10 | //furnished to do so, subject to the following conditions:
11 | //
12 | //The above copyright notice and this permission notice shall be included in all
13 | //copies or substantial portions of the Software.
14 | //
15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | //SOFTWARE.
22 |
23 |
24 | #include
25 | #include
26 |
27 |
28 | using namespace metal;
29 |
30 | struct GlobalParameters
31 | {
32 | uint elementsPerInstance;
33 | };
34 |
35 | // BezierParameters represent a per-curve buffer specifying curve parameters. Note that
36 | // even though the vertex shader is obviously called per-vertex, it actually uses the same
37 | // BezierParameters instance (identified through the instance_id) for all vertexes in a given
38 | // curve.
39 | struct BezierParameters
40 | {
41 | float2 a;
42 | float2 b;
43 | float2 p1;
44 | float2 p2;
45 | float lineThickness;
46 | float4 color;
47 | // The following vectors are used internally on the CPU-side. Since we're sharing memory
48 | // I do not believe this matters much performance-wise, and storing it in this struct
49 | // makes the code simpler on the CPU side. Though it is a recipe for disaster if someone
50 | // changes things without updating this.
51 | float2 unused[4];
52 | float unused2;
53 | };
54 |
55 | struct VertexOut {
56 | float4 pos[[position]];
57 | float4 color;
58 | };
59 |
60 | vertex VertexOut bezier_vertex(constant BezierParameters *allParams[[buffer(0)]],
61 | constant GlobalParameters& globalParams[[buffer(1)]],
62 | uint vertexId [[vertex_id]],
63 | uint instanceId [[instance_id]])
64 | {
65 | // TO DO: Is there no way to ask Metal to give us vertexes per instances?
66 | float t = (float) vertexId / globalParams.elementsPerInstance;
67 |
68 | BezierParameters params = allParams[instanceId];
69 |
70 | // This is a little trick to avoid conditional code. We need to determine which side of the
71 | // triangle we are processing, so as to calculate the correct "side" of the curve, so we just
72 | // check for odd vs. even vertexId values to determine that:
73 | float lineWidth = (1 - (((float) (vertexId % 2)) * 2.0)) * params.lineThickness;
74 |
75 | float2 a = params.a;
76 | float2 b = params.b;
77 |
78 | // We premultiply several values though I doubt it actually does anything performance-wise:
79 | float2 p1 = params.p1 * 3.0;
80 | float2 p2 = params.p2 * 3.0;
81 |
82 | float nt = 1.0f - t;
83 |
84 | float nt_2 = nt * nt;
85 | float nt_3 = nt_2 * nt;
86 |
87 | float t_2 = t * t;
88 | float t_3 = t_2 * t;
89 |
90 | // Calculate a single point in this Bezier curve:
91 | float2 point = a * nt_3 + p1 * nt_2 * t + p2 * nt * t_2 + b * t_3;
92 |
93 | // Calculate the tangent so we can produce a triangle (to achieve a line width greater than 1):
94 | float2 tangent = -3.0 * a * nt_2 + p1 * (1.0 - 4.0 * t + 3.0 * t_2) + p2 * (2.0 * t - 3.0 * t_2) + 3 * b * t_2;
95 |
96 | tangent = normalize(float2(-tangent.y, tangent.x));
97 |
98 | VertexOut vo;
99 |
100 | // Combine the point with the tangent and lineWidth to achieve a properly oriented
101 | // triangle for this point in the curve:
102 | vo.pos.xy = point + (tangent * (lineWidth / 2.0f));
103 | vo.pos.zw = float2(0, 1);
104 | vo.color = params.color;
105 |
106 | return vo;
107 | }
108 |
109 | fragment half4 bezier_fragment(VertexOut params[[stage_in]])
110 | {
111 | return half4(params.color);
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/metal_bezier/MetalBezierView.swift:
--------------------------------------------------------------------------------
1 | //MIT License
2 | //
3 | //Copyright (c) 2016
4 | //
5 | //Permission is hereby granted, free of charge, to any person obtaining a copy
6 | //of this software and associated documentation files (the "Software"), to deal
7 | //in the Software without restriction, including without limitation the rights
8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | //copies of the Software, and to permit persons to whom the Software is
10 | //furnished to do so, subject to the following conditions:
11 | //
12 | //The above copyright notice and this permission notice shall be included in all
13 | //copies or substantial portions of the Software.
14 | //
15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | //SOFTWARE.
22 |
23 |
24 | import UIKit
25 | import MetalKit
26 |
27 | struct BezierParameters
28 | {
29 | // Set coordinateRange to <1.0 to restrict the drawing area
30 | static let coordinateRange : Float = 1.0
31 |
32 | var a : vector_float2 = vector_float2()
33 | var b : vector_float2 = vector_float2()
34 | var p1 : vector_float2 = vector_float2()
35 | var p2 : vector_float2 = vector_float2()
36 |
37 | // This will define line width for all curves:
38 | var lineWidth : Float = 0.10
39 |
40 | var color : vector_float4 = vector_float4()
41 |
42 | private var aMotionVec : vector_float2 = vector_float2()
43 | private var bMotionVec : vector_float2 = vector_float2()
44 | private var p1MotionVec : vector_float2 = vector_float2()
45 | private var p2MotionVec : vector_float2 = vector_float2()
46 |
47 | // This sets the animation speed for the curves:
48 | private var animationSpeed : Float = 0.01
49 |
50 | private var unused : Float = 0.01
51 |
52 |
53 | private func makeRandSpeed() -> Float {
54 | return ((Float(arc4random_uniform(2000000)) / 1000000.0) - 1.0) * animationSpeed
55 | }
56 |
57 | private func makeRandCoord() -> Float {
58 | return (Float(arc4random_uniform(1000000)) / 500000.0 - 1.0) * BezierParameters.coordinateRange
59 | }
60 |
61 | init() {
62 | // Set a random color for this curve:
63 | color = vector_float4(x: Float(arc4random_uniform(1000)) / 1000.0,
64 | y: Float(arc4random_uniform(1000)) / 1000.0,
65 | z: Float(arc4random_uniform(1000)) / 1000.0,
66 | w: 1.0)
67 |
68 | // Start this curve out at a random position and shape:
69 | a.x = makeRandCoord()
70 | a.y = makeRandCoord()
71 |
72 | b.x = makeRandCoord()
73 | b.y = makeRandCoord()
74 |
75 | p1.x = makeRandCoord()
76 | p1.y = makeRandCoord()
77 |
78 | p2.x = makeRandCoord()
79 | p2.y = makeRandCoord()
80 |
81 | // Initialize random motion vectors:
82 | aMotionVec.x = makeRandSpeed()
83 | aMotionVec.y = makeRandSpeed()
84 |
85 | bMotionVec.x = makeRandSpeed()
86 | bMotionVec.y = makeRandSpeed()
87 |
88 | p1MotionVec.x = makeRandSpeed()
89 | p1MotionVec.y = makeRandSpeed()
90 |
91 | p2MotionVec.x = makeRandSpeed()
92 | p2MotionVec.y = makeRandSpeed()
93 | }
94 |
95 | private func animateVector(vector : vector_float2, motionVec : inout vector_float2) -> vector_float2 {
96 | if vector.x >= BezierParameters.coordinateRange || vector.x <= -BezierParameters.coordinateRange {
97 | motionVec.x = -motionVec.x
98 | }
99 | if vector.y >= BezierParameters.coordinateRange || vector.y <= -BezierParameters.coordinateRange {
100 | motionVec.y = -motionVec.y
101 | }
102 |
103 | return vector_float2(x: vector.x + motionVec.x, y: vector.y + motionVec.y)
104 | }
105 |
106 | mutating func animate() {
107 | a = animateVector(vector: a, motionVec: &aMotionVec)
108 | b = animateVector(vector: b, motionVec: &bMotionVec)
109 | p1 = animateVector(vector: p1, motionVec: &p1MotionVec)
110 | p2 = animateVector(vector: p2, motionVec: &p2MotionVec)
111 | }
112 |
113 | init(a : vector_float2, b: vector_float2, p1 : vector_float2, p2 : vector_float2) {
114 | self.a = a
115 | self.b = b
116 | self.p1 = p1
117 | self.p2 = p2
118 | }
119 | }
120 |
121 | struct GlobalParameters {
122 | var elementsPerInstance : UInt
123 | }
124 |
125 | class MetalBezierView: MTKView {
126 | private var commandQueue: MTLCommandQueue! = nil
127 | private var library: MTLLibrary! = nil
128 | private var pipelineDescriptor = MTLRenderPipelineDescriptor()
129 | private var pipelineState : MTLRenderPipelineState! = nil
130 | private var vertexBuffer : MTLBuffer! = nil
131 |
132 | var indices : [UInt16] = [UInt16]()
133 | var indicesBuffer : MTLBuffer?
134 |
135 | var globalParamBuffer : MTLBuffer?
136 |
137 | // This is where we store all curve parameters (including their current positions during
138 | // animation). We use the PageAlignedContiguousArray to directly store and manipulate
139 | // them in shared memory.
140 | var params : PageAlignedContiguousArray = PageAlignedContiguousArray(repeating: BezierParameters(), count: 2000)
141 | var paramBuffer : MTLBuffer?
142 |
143 | override init(frame frameRect: CGRect, device: MTLDevice?)
144 | {
145 | super.init(frame: frameRect, device: device)
146 | configureWithDevice(device!)
147 | }
148 |
149 | required init(coder: NSCoder)
150 | {
151 | super.init(coder: coder)
152 | configureWithDevice(MTLCreateSystemDefaultDevice()!)
153 | }
154 |
155 | private func configureWithDevice(_ device : MTLDevice) {
156 | self.clearColor = MTLClearColor.init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
157 | self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
158 | self.framebufferOnly = true
159 | self.colorPixelFormat = .bgra8Unorm
160 |
161 | // Run with 4x MSAA:
162 | self.sampleCount = 4
163 |
164 | self.preferredFramesPerSecond = 60
165 |
166 | self.device = device
167 | }
168 |
169 | override var device: MTLDevice! {
170 | didSet {
171 | super.device = device
172 | commandQueue = (self.device?.makeCommandQueue())!
173 |
174 | library = device?.newDefaultLibrary()
175 | pipelineDescriptor.vertexFunction = library?.makeFunction(name: "bezier_vertex")
176 | pipelineDescriptor.fragmentFunction = library?.makeFunction(name: "bezier_fragment")
177 | pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
178 |
179 | // Run with 4x MSAA:
180 | pipelineDescriptor.sampleCount = 4
181 |
182 | do {
183 | try pipelineState = device?.makeRenderPipelineState(descriptor: pipelineDescriptor)
184 | }
185 | catch {
186 |
187 | }
188 | for (index, _) in params.enumerated() {
189 | params[index] = BezierParameters()
190 | }
191 |
192 | paramBuffer = device.makeBufferWithPageAlignedArray(params)
193 |
194 | var currentIndex : UInt16 = 0
195 |
196 | // Set how many "elements" are to be used for each curve. Normally we would
197 | // calculate this per curve, but since we're using the indexed primitives
198 | // approach, we need a fixed number of vertices per curve. Note that this is
199 | // the number of triangles, not vertexes:
200 | var globalParams = GlobalParameters(elementsPerInstance: 200)
201 | globalParamBuffer = (self.device?.makeBuffer(bytes: &globalParams,
202 | length: MemoryLayout.size,
203 | options: .storageModeShared))
204 |
205 | repeat {
206 | indices.append(currentIndex)
207 | indices.append(currentIndex + 1)
208 | indices.append(currentIndex + 2)
209 | currentIndex += 1
210 | } while indices.count < Int(globalParams.elementsPerInstance * 3)
211 |
212 | let indicesDataSize = MemoryLayout.size * indices.count
213 | indicesBuffer = (self.device?.makeBuffer(bytes: indices,
214 | length: indicesDataSize,
215 | options: .storageModeShared))
216 | }
217 | }
218 |
219 | override func draw(_ rect: CGRect) {
220 |
221 | // Animate all of our curves. No need to reload this into the GPU for each frame.
222 | // The params are in shared memory so our modifications will be automatically visible
223 | // to the vertex shader.
224 | for (index, _) in params.enumerated() {
225 | params[index].animate()
226 | }
227 |
228 | let commandBuffer = commandQueue!.makeCommandBuffer()
229 |
230 | let renderPassDescriptor = self.currentRenderPassDescriptor
231 |
232 | if renderPassDescriptor == nil {
233 | return
234 | }
235 |
236 |
237 | let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
238 |
239 | renderEncoder.setRenderPipelineState(pipelineState)
240 |
241 | renderEncoder.setVertexBuffer(paramBuffer, offset: 0, at: 0)
242 | renderEncoder.setVertexBuffer(globalParamBuffer, offset: 0, at: 1)
243 |
244 | // Enable this to see the actual triangles instead of a solid curve:
245 | //renderEncoder.setTriangleFillMode(.lines)
246 |
247 | renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indicesBuffer!, indexBufferOffset: 0, instanceCount: params.count)
248 |
249 | renderEncoder.endEncoding()
250 |
251 | commandBuffer.present(self.currentDrawable!)
252 | commandBuffer.commit()
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/metal_bezier/PageAlignedArray.swift:
--------------------------------------------------------------------------------
1 | //MIT License
2 | //
3 | //Copyright (c) 2016
4 | //
5 | //Permission is hereby granted, free of charge, to any person obtaining a copy
6 | //of this software and associated documentation files (the "Software"), to deal
7 | //in the Software without restriction, including without limitation the rights
8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | //copies of the Software, and to permit persons to whom the Software is
10 | //furnished to do so, subject to the following conditions:
11 | //
12 | //The above copyright notice and this permission notice shall be included in all
13 | //copies or substantial portions of the Software.
14 | //
15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | //SOFTWARE.
22 |
23 | import CoreFoundation
24 | import Metal
25 |
26 | class PageAlignedArrayImpl {
27 | var space: Int
28 | var ptr: UnsafeMutablePointer
29 |
30 | static private func alignedAlloc(count: Int) -> UnsafeMutablePointer {
31 | var newAddr:UnsafeMutableRawPointer?
32 | let alignment : Int = Int(getpagesize())
33 | var size : Int
34 |
35 | if count == 0 {
36 | size = MemoryLayout.stride / Int(getpagesize())
37 |
38 | if MemoryLayout.stride % Int(getpagesize()) != 0 {
39 | size += Int(getpagesize())
40 | }
41 | } else {
42 | size = Int(count * MemoryLayout.stride)
43 | }
44 |
45 | posix_memalign(&newAddr, alignment, size)
46 |
47 | return newAddr!.assumingMemoryBound(to: T.self)
48 | }
49 |
50 | static private func freeAlignedAlloc(addr : UnsafeMutablePointer) {
51 | free(addr)
52 | }
53 |
54 |
55 | init(count: Int = 0, ptr: UnsafeMutablePointer? = nil) {
56 | self.count = count
57 | self.space = Int(getpagesize()) / MemoryLayout.stride
58 |
59 | self.ptr = PageAlignedArrayImpl.alignedAlloc(count: count)
60 |
61 | if ptr != nil {
62 | self.ptr.initialize(from: ptr!, count: count)
63 | }
64 | }
65 |
66 | var count : Int {
67 | didSet {
68 | if space <= count {
69 | let newSpace = count * 2
70 | let newPtr = PageAlignedArrayImpl.alignedAlloc(count: newSpace)
71 |
72 | newPtr.moveInitialize(from: ptr, count: oldValue)
73 |
74 | PageAlignedArrayImpl.freeAlignedAlloc(addr: ptr)
75 | ptr = newPtr
76 | space = newSpace
77 | }
78 | }
79 | }
80 |
81 | func copy() -> PageAlignedArrayImpl {
82 | return PageAlignedArrayImpl(count: count, ptr: ptr)
83 | }
84 |
85 | deinit {
86 | ptr.deinitialize(count: count)
87 | PageAlignedArrayImpl.freeAlignedAlloc(addr: ptr)
88 | }
89 | }
90 |
91 | struct PageAlignedContiguousArray: RangeReplaceableCollection {
92 | private var impl: PageAlignedArrayImpl = PageAlignedArrayImpl(count: 0)
93 |
94 | /// Replaces the specified subrange of elements with the given collection.
95 | ///
96 | /// This method has the effect of removing the specified range of elements
97 | /// from the collection and inserting the new elements at the same location.
98 | /// The number of new elements need not match the number of elements being
99 | /// removed.
100 | ///
101 | /// In this example, three elements in the middle of an array of integers are
102 | /// replaced by the five elements of a `Repeated` instance.
103 | ///
104 | /// var nums = [10, 20, 30, 40, 50]
105 | /// nums.replaceSubrange(1...3, with: repeatElement(1, count: 5))
106 | /// print(nums)
107 | /// // Prints "[10, 1, 1, 1, 1, 1, 50]"
108 | ///
109 | /// If you pass a zero-length range as the `subrange` parameter, this method
110 | /// inserts the elements of `newElements` at `subrange.startIndex`. Calling
111 | /// the `insert(contentsOf:at:)` method instead is preferred.
112 | ///
113 | /// Likewise, if you pass a zero-length collection as the `newElements`
114 | /// parameter, this method removes the elements in the given subrange
115 | /// without replacement. Calling the `removeSubrange(_:)` method instead is
116 | /// preferred.
117 | ///
118 | /// Calling this method may invalidate any existing indices for use with this
119 | /// collection.
120 | ///
121 | /// - Parameters:
122 | /// - subrange: The subrange of the collection to replace. The bounds of
123 | /// the range must be valid indices of the collection.
124 | /// - newElements: The new elements to add to the collection.
125 | ///
126 | /// - Complexity: O(*m*), where *m* is the combined length of the collection
127 | /// and `newElements`. If the call to `replaceSubrange` simply appends the
128 | /// contents of `newElements` to the collection, the complexity is O(*n*),
129 | /// where *n* is the length of `newElements`.
130 | public mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C : Collection, C.Iterator.Element == T {
131 | let newCount = newElements.count as! Int
132 | let oldCount = self.count
133 | let eraseCount = subrange.count
134 |
135 | let growth = newCount - eraseCount
136 | impl.count = oldCount + growth
137 |
138 | let elements = impl.ptr
139 | let oldTailIndex = subrange.upperBound
140 | let oldTailStart = elements + oldTailIndex
141 | let newTailIndex = oldTailIndex + growth
142 | let newTailStart = oldTailStart + growth
143 | let tailCount = oldCount - subrange.upperBound
144 |
145 | if growth > 0 {
146 | // Slide the tail part of the buffer forwards, in reverse order
147 | // so as not to self-clobber.
148 | newTailStart.moveInitialize(from: oldTailStart, count: tailCount)
149 |
150 | // Assign over the original subRange
151 | var i = newElements.startIndex
152 | for j in CountableRange(subrange) {
153 | elements[j] = newElements[i]
154 | newElements.formIndex(after: &i)
155 | }
156 | // Initialize the hole left by sliding the tail forward
157 | for j in oldTailIndex.. shrinkage { // If the tail length exceeds the shrinkage
180 |
181 | // Assign over the rest of the replaced range with the first
182 | // part of the tail.
183 | newTailStart.moveAssign(from: oldTailStart, count: shrinkage)
184 |
185 | // Slide the rest of the tail back
186 | oldTailStart.moveInitialize(
187 | from: oldTailStart + shrinkage, count: tailCount - shrinkage)
188 | }
189 | else { // Tail fits within erased elements
190 | // Assign over the start of the replaced range with the tail
191 | newTailStart.moveAssign(from: oldTailStart, count: tailCount)
192 |
193 | // Destroy elements remaining after the tail in subRange
194 | (newTailStart + tailCount).deinitialize(
195 | count: shrinkage - tailCount)
196 | }
197 | }
198 | }
199 |
200 | /// Returns the position immediately after the given index.
201 | ///
202 | /// - Parameter i: A valid index of the collection. `i` must be less than
203 | /// `endIndex`.
204 | /// - Returns: The index value immediately after `i`.
205 | public func index(after i: Int) -> Int {
206 | return i + 1
207 | }
208 |
209 | var buffer : UnsafeMutablePointer {
210 | return impl.ptr
211 | }
212 |
213 | var bufferLength : Int {
214 | return impl.space * MemoryLayout.stride
215 | }
216 |
217 |
218 | var count: Int {
219 | return impl.count
220 | }
221 |
222 | subscript(index: Int) -> T {
223 | get {
224 | assert (index < count, "Array index out of range")
225 | return impl.ptr[index]
226 | }
227 | mutating set {
228 | assert (index < count, "Array index out of range")
229 | impl.ptr[index] = newValue
230 | }
231 | }
232 |
233 | var description: String {
234 | return String(format: "Aligned buffer: %x", impl.ptr)
235 | }
236 |
237 | typealias Index = Int
238 |
239 | var startIndex: Index {
240 | return 0
241 | }
242 |
243 | var endIndex: Index {
244 | return count
245 | }
246 |
247 | typealias Generator = AnyIterator
248 |
249 | func generate() -> Generator {
250 | var index = 0
251 | return AnyIterator {
252 | if index < self.count {
253 | index += 1
254 | return self[index]
255 | } else {
256 | return nil
257 | }
258 | }
259 | }
260 | }
261 |
262 | extension PageAlignedContiguousArray : ExpressibleByArrayLiteral {
263 | public init(arrayLiteral elements: T...) {
264 | self.init()
265 | for element in elements {
266 | append(element)
267 | }
268 | }
269 | }
270 |
271 | extension MTLDevice {
272 | func makeBufferWithPageAlignedArray(_ array: PageAlignedContiguousArray) -> MTLBuffer? {
273 | let pageSize = UInt(getpagesize())
274 | let pageSizeBitmask = UInt(getpagesize()) - 1
275 |
276 | var calculatedBufferLength = UInt(array.bufferLength)
277 | if (calculatedBufferLength & pageSizeBitmask) != 0 {
278 | // WARNING: I BELIEVE this is safe to do. Metal wants a fully page aligned buffer length consisting of a
279 | // page aligned pointer and a page-aligned length. If the length is not page aligned, I round it up to the
280 | // next page size here. I figure it would't actually read those extra bytes. Then again why is this
281 | // requirement there in the first place?
282 | calculatedBufferLength &= ~(pageSize - 1)
283 | calculatedBufferLength += pageSize
284 | }
285 | return self.makeBuffer(bytesNoCopy: array.buffer, length: Int(calculatedBufferLength), options: .storageModeShared, deallocator: nil)
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/ios_metal_bezier_renderer.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5130CC661DCEEC5200FA29CC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5130CC651DCEEC5200FA29CC /* AppDelegate.swift */; };
11 | 5130CC681DCEEC5200FA29CC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5130CC671DCEEC5200FA29CC /* ViewController.swift */; };
12 | 5130CC6B1DCEEC5200FA29CC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5130CC691DCEEC5200FA29CC /* Main.storyboard */; };
13 | 5130CC6D1DCEEC5200FA29CC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5130CC6C1DCEEC5200FA29CC /* Assets.xcassets */; };
14 | 5130CC701DCEEC5200FA29CC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5130CC6E1DCEEC5200FA29CC /* LaunchScreen.storyboard */; };
15 | 5130CC941DCEEC8400FA29CC /* MetalBezierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5130CC931DCEEC8400FA29CC /* MetalBezierView.swift */; };
16 | 5130CC961DCEED4F00FA29CC /* bezier_shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 5130CC951DCEED4F00FA29CC /* bezier_shaders.metal */; };
17 | 5130CC981DD10BC000FA29CC /* PageAlignedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5130CC971DD10BC000FA29CC /* PageAlignedArray.swift */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXContainerItemProxy section */
21 | 5130CC771DCEEC5200FA29CC /* PBXContainerItemProxy */ = {
22 | isa = PBXContainerItemProxy;
23 | containerPortal = 5130CC5A1DCEEC5200FA29CC /* Project object */;
24 | proxyType = 1;
25 | remoteGlobalIDString = 5130CC611DCEEC5200FA29CC;
26 | remoteInfo = metal_bezier;
27 | };
28 | 5130CC821DCEEC5200FA29CC /* PBXContainerItemProxy */ = {
29 | isa = PBXContainerItemProxy;
30 | containerPortal = 5130CC5A1DCEEC5200FA29CC /* Project object */;
31 | proxyType = 1;
32 | remoteGlobalIDString = 5130CC611DCEEC5200FA29CC;
33 | remoteInfo = metal_bezier;
34 | };
35 | /* End PBXContainerItemProxy section */
36 |
37 | /* Begin PBXFileReference section */
38 | 5130CC621DCEEC5200FA29CC /* ios_metal_bezier_renderer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ios_metal_bezier_renderer.app; sourceTree = BUILT_PRODUCTS_DIR; };
39 | 5130CC651DCEEC5200FA29CC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
40 | 5130CC671DCEEC5200FA29CC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
41 | 5130CC6A1DCEEC5200FA29CC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 5130CC6C1DCEEC5200FA29CC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 5130CC6F1DCEEC5200FA29CC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 5130CC711DCEEC5200FA29CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | 5130CC761DCEEC5200FA29CC /* ios_metal_bezier_rendererTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ios_metal_bezier_rendererTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
46 | 5130CC811DCEEC5200FA29CC /* ios_metal_bezier_rendererUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ios_metal_bezier_rendererUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 5130CC931DCEEC8400FA29CC /* MetalBezierView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalBezierView.swift; sourceTree = ""; };
48 | 5130CC951DCEED4F00FA29CC /* bezier_shaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = bezier_shaders.metal; sourceTree = ""; };
49 | 5130CC971DD10BC000FA29CC /* PageAlignedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageAlignedArray.swift; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | 5130CC5F1DCEEC5200FA29CC /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | );
58 | runOnlyForDeploymentPostprocessing = 0;
59 | };
60 | 5130CC731DCEEC5200FA29CC /* Frameworks */ = {
61 | isa = PBXFrameworksBuildPhase;
62 | buildActionMask = 2147483647;
63 | files = (
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | 5130CC7E1DCEEC5200FA29CC /* Frameworks */ = {
68 | isa = PBXFrameworksBuildPhase;
69 | buildActionMask = 2147483647;
70 | files = (
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXFrameworksBuildPhase section */
75 |
76 | /* Begin PBXGroup section */
77 | 5130CC591DCEEC5200FA29CC = {
78 | isa = PBXGroup;
79 | children = (
80 | 5130CC641DCEEC5200FA29CC /* ios_metal_bezier_renderer */,
81 | 5130CC631DCEEC5200FA29CC /* Products */,
82 | );
83 | sourceTree = "";
84 | };
85 | 5130CC631DCEEC5200FA29CC /* Products */ = {
86 | isa = PBXGroup;
87 | children = (
88 | 5130CC621DCEEC5200FA29CC /* ios_metal_bezier_renderer.app */,
89 | 5130CC761DCEEC5200FA29CC /* ios_metal_bezier_rendererTests.xctest */,
90 | 5130CC811DCEEC5200FA29CC /* ios_metal_bezier_rendererUITests.xctest */,
91 | );
92 | name = Products;
93 | sourceTree = "";
94 | };
95 | 5130CC641DCEEC5200FA29CC /* ios_metal_bezier_renderer */ = {
96 | isa = PBXGroup;
97 | children = (
98 | 5130CC931DCEEC8400FA29CC /* MetalBezierView.swift */,
99 | 5130CC951DCEED4F00FA29CC /* bezier_shaders.metal */,
100 | 5130CC971DD10BC000FA29CC /* PageAlignedArray.swift */,
101 | 5130CC651DCEEC5200FA29CC /* AppDelegate.swift */,
102 | 5130CC671DCEEC5200FA29CC /* ViewController.swift */,
103 | 5130CC691DCEEC5200FA29CC /* Main.storyboard */,
104 | 5130CC6C1DCEEC5200FA29CC /* Assets.xcassets */,
105 | 5130CC6E1DCEEC5200FA29CC /* LaunchScreen.storyboard */,
106 | 5130CC711DCEEC5200FA29CC /* Info.plist */,
107 | );
108 | name = ios_metal_bezier_renderer;
109 | path = metal_bezier;
110 | sourceTree = "";
111 | };
112 | /* End PBXGroup section */
113 |
114 | /* Begin PBXNativeTarget section */
115 | 5130CC611DCEEC5200FA29CC /* ios_metal_bezier_renderer */ = {
116 | isa = PBXNativeTarget;
117 | buildConfigurationList = 5130CC8A1DCEEC5300FA29CC /* Build configuration list for PBXNativeTarget "ios_metal_bezier_renderer" */;
118 | buildPhases = (
119 | 5130CC5E1DCEEC5200FA29CC /* Sources */,
120 | 5130CC5F1DCEEC5200FA29CC /* Frameworks */,
121 | 5130CC601DCEEC5200FA29CC /* Resources */,
122 | );
123 | buildRules = (
124 | );
125 | dependencies = (
126 | );
127 | name = ios_metal_bezier_renderer;
128 | productName = metal_bezier;
129 | productReference = 5130CC621DCEEC5200FA29CC /* ios_metal_bezier_renderer.app */;
130 | productType = "com.apple.product-type.application";
131 | };
132 | 5130CC751DCEEC5200FA29CC /* ios_metal_bezier_rendererTests */ = {
133 | isa = PBXNativeTarget;
134 | buildConfigurationList = 5130CC8D1DCEEC5300FA29CC /* Build configuration list for PBXNativeTarget "ios_metal_bezier_rendererTests" */;
135 | buildPhases = (
136 | 5130CC721DCEEC5200FA29CC /* Sources */,
137 | 5130CC731DCEEC5200FA29CC /* Frameworks */,
138 | 5130CC741DCEEC5200FA29CC /* Resources */,
139 | );
140 | buildRules = (
141 | );
142 | dependencies = (
143 | 5130CC781DCEEC5200FA29CC /* PBXTargetDependency */,
144 | );
145 | name = ios_metal_bezier_rendererTests;
146 | productName = metal_bezierTests;
147 | productReference = 5130CC761DCEEC5200FA29CC /* ios_metal_bezier_rendererTests.xctest */;
148 | productType = "com.apple.product-type.bundle.unit-test";
149 | };
150 | 5130CC801DCEEC5200FA29CC /* ios_metal_bezier_rendererUITests */ = {
151 | isa = PBXNativeTarget;
152 | buildConfigurationList = 5130CC901DCEEC5300FA29CC /* Build configuration list for PBXNativeTarget "ios_metal_bezier_rendererUITests" */;
153 | buildPhases = (
154 | 5130CC7D1DCEEC5200FA29CC /* Sources */,
155 | 5130CC7E1DCEEC5200FA29CC /* Frameworks */,
156 | 5130CC7F1DCEEC5200FA29CC /* Resources */,
157 | );
158 | buildRules = (
159 | );
160 | dependencies = (
161 | 5130CC831DCEEC5200FA29CC /* PBXTargetDependency */,
162 | );
163 | name = ios_metal_bezier_rendererUITests;
164 | productName = metal_bezierUITests;
165 | productReference = 5130CC811DCEEC5200FA29CC /* ios_metal_bezier_rendererUITests.xctest */;
166 | productType = "com.apple.product-type.bundle.ui-testing";
167 | };
168 | /* End PBXNativeTarget section */
169 |
170 | /* Begin PBXProject section */
171 | 5130CC5A1DCEEC5200FA29CC /* Project object */ = {
172 | isa = PBXProject;
173 | attributes = {
174 | LastSwiftUpdateCheck = 0810;
175 | LastUpgradeCheck = 0810;
176 | ORGANIZATIONNAME = "Eldad Eilam";
177 | TargetAttributes = {
178 | 5130CC611DCEEC5200FA29CC = {
179 | CreatedOnToolsVersion = 8.1;
180 | DevelopmentTeam = E54FNFV6AU;
181 | ProvisioningStyle = Automatic;
182 | };
183 | 5130CC751DCEEC5200FA29CC = {
184 | CreatedOnToolsVersion = 8.1;
185 | ProvisioningStyle = Automatic;
186 | TestTargetID = 5130CC611DCEEC5200FA29CC;
187 | };
188 | 5130CC801DCEEC5200FA29CC = {
189 | CreatedOnToolsVersion = 8.1;
190 | ProvisioningStyle = Automatic;
191 | TestTargetID = 5130CC611DCEEC5200FA29CC;
192 | };
193 | };
194 | };
195 | buildConfigurationList = 5130CC5D1DCEEC5200FA29CC /* Build configuration list for PBXProject "ios_metal_bezier_renderer" */;
196 | compatibilityVersion = "Xcode 3.2";
197 | developmentRegion = English;
198 | hasScannedForEncodings = 0;
199 | knownRegions = (
200 | en,
201 | Base,
202 | );
203 | mainGroup = 5130CC591DCEEC5200FA29CC;
204 | productRefGroup = 5130CC631DCEEC5200FA29CC /* Products */;
205 | projectDirPath = "";
206 | projectRoot = "";
207 | targets = (
208 | 5130CC611DCEEC5200FA29CC /* ios_metal_bezier_renderer */,
209 | 5130CC751DCEEC5200FA29CC /* ios_metal_bezier_rendererTests */,
210 | 5130CC801DCEEC5200FA29CC /* ios_metal_bezier_rendererUITests */,
211 | );
212 | };
213 | /* End PBXProject section */
214 |
215 | /* Begin PBXResourcesBuildPhase section */
216 | 5130CC601DCEEC5200FA29CC /* Resources */ = {
217 | isa = PBXResourcesBuildPhase;
218 | buildActionMask = 2147483647;
219 | files = (
220 | 5130CC701DCEEC5200FA29CC /* LaunchScreen.storyboard in Resources */,
221 | 5130CC6D1DCEEC5200FA29CC /* Assets.xcassets in Resources */,
222 | 5130CC6B1DCEEC5200FA29CC /* Main.storyboard in Resources */,
223 | );
224 | runOnlyForDeploymentPostprocessing = 0;
225 | };
226 | 5130CC741DCEEC5200FA29CC /* Resources */ = {
227 | isa = PBXResourcesBuildPhase;
228 | buildActionMask = 2147483647;
229 | files = (
230 | );
231 | runOnlyForDeploymentPostprocessing = 0;
232 | };
233 | 5130CC7F1DCEEC5200FA29CC /* Resources */ = {
234 | isa = PBXResourcesBuildPhase;
235 | buildActionMask = 2147483647;
236 | files = (
237 | );
238 | runOnlyForDeploymentPostprocessing = 0;
239 | };
240 | /* End PBXResourcesBuildPhase section */
241 |
242 | /* Begin PBXSourcesBuildPhase section */
243 | 5130CC5E1DCEEC5200FA29CC /* Sources */ = {
244 | isa = PBXSourcesBuildPhase;
245 | buildActionMask = 2147483647;
246 | files = (
247 | 5130CC941DCEEC8400FA29CC /* MetalBezierView.swift in Sources */,
248 | 5130CC681DCEEC5200FA29CC /* ViewController.swift in Sources */,
249 | 5130CC661DCEEC5200FA29CC /* AppDelegate.swift in Sources */,
250 | 5130CC961DCEED4F00FA29CC /* bezier_shaders.metal in Sources */,
251 | 5130CC981DD10BC000FA29CC /* PageAlignedArray.swift in Sources */,
252 | );
253 | runOnlyForDeploymentPostprocessing = 0;
254 | };
255 | 5130CC721DCEEC5200FA29CC /* Sources */ = {
256 | isa = PBXSourcesBuildPhase;
257 | buildActionMask = 2147483647;
258 | files = (
259 | );
260 | runOnlyForDeploymentPostprocessing = 0;
261 | };
262 | 5130CC7D1DCEEC5200FA29CC /* Sources */ = {
263 | isa = PBXSourcesBuildPhase;
264 | buildActionMask = 2147483647;
265 | files = (
266 | );
267 | runOnlyForDeploymentPostprocessing = 0;
268 | };
269 | /* End PBXSourcesBuildPhase section */
270 |
271 | /* Begin PBXTargetDependency section */
272 | 5130CC781DCEEC5200FA29CC /* PBXTargetDependency */ = {
273 | isa = PBXTargetDependency;
274 | target = 5130CC611DCEEC5200FA29CC /* ios_metal_bezier_renderer */;
275 | targetProxy = 5130CC771DCEEC5200FA29CC /* PBXContainerItemProxy */;
276 | };
277 | 5130CC831DCEEC5200FA29CC /* PBXTargetDependency */ = {
278 | isa = PBXTargetDependency;
279 | target = 5130CC611DCEEC5200FA29CC /* ios_metal_bezier_renderer */;
280 | targetProxy = 5130CC821DCEEC5200FA29CC /* PBXContainerItemProxy */;
281 | };
282 | /* End PBXTargetDependency section */
283 |
284 | /* Begin PBXVariantGroup section */
285 | 5130CC691DCEEC5200FA29CC /* Main.storyboard */ = {
286 | isa = PBXVariantGroup;
287 | children = (
288 | 5130CC6A1DCEEC5200FA29CC /* Base */,
289 | );
290 | name = Main.storyboard;
291 | sourceTree = "";
292 | };
293 | 5130CC6E1DCEEC5200FA29CC /* LaunchScreen.storyboard */ = {
294 | isa = PBXVariantGroup;
295 | children = (
296 | 5130CC6F1DCEEC5200FA29CC /* Base */,
297 | );
298 | name = LaunchScreen.storyboard;
299 | sourceTree = "";
300 | };
301 | /* End PBXVariantGroup section */
302 |
303 | /* Begin XCBuildConfiguration section */
304 | 5130CC881DCEEC5300FA29CC /* Debug */ = {
305 | isa = XCBuildConfiguration;
306 | buildSettings = {
307 | ALWAYS_SEARCH_USER_PATHS = NO;
308 | CLANG_ANALYZER_NONNULL = YES;
309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
310 | CLANG_CXX_LIBRARY = "libc++";
311 | CLANG_ENABLE_MODULES = YES;
312 | CLANG_ENABLE_OBJC_ARC = YES;
313 | CLANG_WARN_BOOL_CONVERSION = YES;
314 | CLANG_WARN_CONSTANT_CONVERSION = YES;
315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
316 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
317 | CLANG_WARN_EMPTY_BODY = YES;
318 | CLANG_WARN_ENUM_CONVERSION = YES;
319 | CLANG_WARN_INFINITE_RECURSION = YES;
320 | CLANG_WARN_INT_CONVERSION = YES;
321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
322 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
323 | CLANG_WARN_UNREACHABLE_CODE = YES;
324 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
325 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
326 | COPY_PHASE_STRIP = NO;
327 | DEBUG_INFORMATION_FORMAT = dwarf;
328 | ENABLE_STRICT_OBJC_MSGSEND = YES;
329 | ENABLE_TESTABILITY = YES;
330 | GCC_C_LANGUAGE_STANDARD = gnu99;
331 | GCC_DYNAMIC_NO_PIC = NO;
332 | GCC_NO_COMMON_BLOCKS = YES;
333 | GCC_OPTIMIZATION_LEVEL = 0;
334 | GCC_PREPROCESSOR_DEFINITIONS = (
335 | "DEBUG=1",
336 | "$(inherited)",
337 | );
338 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
339 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
340 | GCC_WARN_UNDECLARED_SELECTOR = YES;
341 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
342 | GCC_WARN_UNUSED_FUNCTION = YES;
343 | GCC_WARN_UNUSED_VARIABLE = YES;
344 | IPHONEOS_DEPLOYMENT_TARGET = 10.1;
345 | MTL_ENABLE_DEBUG_INFO = YES;
346 | ONLY_ACTIVE_ARCH = YES;
347 | SDKROOT = iphoneos;
348 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
349 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
350 | TARGETED_DEVICE_FAMILY = "1,2";
351 | };
352 | name = Debug;
353 | };
354 | 5130CC891DCEEC5300FA29CC /* Release */ = {
355 | isa = XCBuildConfiguration;
356 | buildSettings = {
357 | ALWAYS_SEARCH_USER_PATHS = NO;
358 | CLANG_ANALYZER_NONNULL = YES;
359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
360 | CLANG_CXX_LIBRARY = "libc++";
361 | CLANG_ENABLE_MODULES = YES;
362 | CLANG_ENABLE_OBJC_ARC = YES;
363 | CLANG_WARN_BOOL_CONVERSION = YES;
364 | CLANG_WARN_CONSTANT_CONVERSION = YES;
365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
366 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
367 | CLANG_WARN_EMPTY_BODY = YES;
368 | CLANG_WARN_ENUM_CONVERSION = YES;
369 | CLANG_WARN_INFINITE_RECURSION = YES;
370 | CLANG_WARN_INT_CONVERSION = YES;
371 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
372 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
373 | CLANG_WARN_UNREACHABLE_CODE = YES;
374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
375 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
376 | COPY_PHASE_STRIP = NO;
377 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
378 | ENABLE_NS_ASSERTIONS = NO;
379 | ENABLE_STRICT_OBJC_MSGSEND = YES;
380 | GCC_C_LANGUAGE_STANDARD = gnu99;
381 | GCC_NO_COMMON_BLOCKS = YES;
382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
384 | GCC_WARN_UNDECLARED_SELECTOR = YES;
385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
386 | GCC_WARN_UNUSED_FUNCTION = YES;
387 | GCC_WARN_UNUSED_VARIABLE = YES;
388 | IPHONEOS_DEPLOYMENT_TARGET = 10.1;
389 | MTL_ENABLE_DEBUG_INFO = NO;
390 | SDKROOT = iphoneos;
391 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
392 | TARGETED_DEVICE_FAMILY = "1,2";
393 | VALIDATE_PRODUCT = YES;
394 | };
395 | name = Release;
396 | };
397 | 5130CC8B1DCEEC5300FA29CC /* Debug */ = {
398 | isa = XCBuildConfiguration;
399 | buildSettings = {
400 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
401 | DEVELOPMENT_TEAM = E54FNFV6AU;
402 | INFOPLIST_FILE = metal_bezier/Info.plist;
403 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
404 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
405 | PRODUCT_BUNDLE_IDENTIFIER = "com.eldade.mac-metal-tests.metal-bezier";
406 | PRODUCT_NAME = "$(TARGET_NAME)";
407 | SWIFT_VERSION = 3.0;
408 | };
409 | name = Debug;
410 | };
411 | 5130CC8C1DCEEC5300FA29CC /* Release */ = {
412 | isa = XCBuildConfiguration;
413 | buildSettings = {
414 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
415 | DEVELOPMENT_TEAM = E54FNFV6AU;
416 | INFOPLIST_FILE = metal_bezier/Info.plist;
417 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
418 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
419 | PRODUCT_BUNDLE_IDENTIFIER = "com.eldade.mac-metal-tests.metal-bezier";
420 | PRODUCT_NAME = "$(TARGET_NAME)";
421 | SWIFT_VERSION = 3.0;
422 | };
423 | name = Release;
424 | };
425 | 5130CC8E1DCEEC5300FA29CC /* Debug */ = {
426 | isa = XCBuildConfiguration;
427 | buildSettings = {
428 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
429 | BUNDLE_LOADER = "$(TEST_HOST)";
430 | INFOPLIST_FILE = metal_bezierTests/Info.plist;
431 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
432 | PRODUCT_BUNDLE_IDENTIFIER = "com.eldade.mac-metal-tests.metal-bezierTests";
433 | PRODUCT_NAME = "$(TARGET_NAME)";
434 | SWIFT_VERSION = 3.0;
435 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ios_metal_bezier_renderer.app/ios_metal_bezier_renderer";
436 | };
437 | name = Debug;
438 | };
439 | 5130CC8F1DCEEC5300FA29CC /* Release */ = {
440 | isa = XCBuildConfiguration;
441 | buildSettings = {
442 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
443 | BUNDLE_LOADER = "$(TEST_HOST)";
444 | INFOPLIST_FILE = metal_bezierTests/Info.plist;
445 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
446 | PRODUCT_BUNDLE_IDENTIFIER = "com.eldade.mac-metal-tests.metal-bezierTests";
447 | PRODUCT_NAME = "$(TARGET_NAME)";
448 | SWIFT_VERSION = 3.0;
449 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ios_metal_bezier_renderer.app/ios_metal_bezier_renderer";
450 | };
451 | name = Release;
452 | };
453 | 5130CC911DCEEC5300FA29CC /* Debug */ = {
454 | isa = XCBuildConfiguration;
455 | buildSettings = {
456 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
457 | INFOPLIST_FILE = metal_bezierUITests/Info.plist;
458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
459 | PRODUCT_BUNDLE_IDENTIFIER = "com.eldade.mac-metal-tests.metal-bezierUITests";
460 | PRODUCT_NAME = "$(TARGET_NAME)";
461 | SWIFT_VERSION = 3.0;
462 | TEST_TARGET_NAME = metal_bezier;
463 | };
464 | name = Debug;
465 | };
466 | 5130CC921DCEEC5300FA29CC /* Release */ = {
467 | isa = XCBuildConfiguration;
468 | buildSettings = {
469 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
470 | INFOPLIST_FILE = metal_bezierUITests/Info.plist;
471 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
472 | PRODUCT_BUNDLE_IDENTIFIER = "com.eldade.mac-metal-tests.metal-bezierUITests";
473 | PRODUCT_NAME = "$(TARGET_NAME)";
474 | SWIFT_VERSION = 3.0;
475 | TEST_TARGET_NAME = metal_bezier;
476 | };
477 | name = Release;
478 | };
479 | /* End XCBuildConfiguration section */
480 |
481 | /* Begin XCConfigurationList section */
482 | 5130CC5D1DCEEC5200FA29CC /* Build configuration list for PBXProject "ios_metal_bezier_renderer" */ = {
483 | isa = XCConfigurationList;
484 | buildConfigurations = (
485 | 5130CC881DCEEC5300FA29CC /* Debug */,
486 | 5130CC891DCEEC5300FA29CC /* Release */,
487 | );
488 | defaultConfigurationIsVisible = 0;
489 | defaultConfigurationName = Release;
490 | };
491 | 5130CC8A1DCEEC5300FA29CC /* Build configuration list for PBXNativeTarget "ios_metal_bezier_renderer" */ = {
492 | isa = XCConfigurationList;
493 | buildConfigurations = (
494 | 5130CC8B1DCEEC5300FA29CC /* Debug */,
495 | 5130CC8C1DCEEC5300FA29CC /* Release */,
496 | );
497 | defaultConfigurationIsVisible = 0;
498 | defaultConfigurationName = Release;
499 | };
500 | 5130CC8D1DCEEC5300FA29CC /* Build configuration list for PBXNativeTarget "ios_metal_bezier_rendererTests" */ = {
501 | isa = XCConfigurationList;
502 | buildConfigurations = (
503 | 5130CC8E1DCEEC5300FA29CC /* Debug */,
504 | 5130CC8F1DCEEC5300FA29CC /* Release */,
505 | );
506 | defaultConfigurationIsVisible = 0;
507 | defaultConfigurationName = Release;
508 | };
509 | 5130CC901DCEEC5300FA29CC /* Build configuration list for PBXNativeTarget "ios_metal_bezier_rendererUITests" */ = {
510 | isa = XCConfigurationList;
511 | buildConfigurations = (
512 | 5130CC911DCEEC5300FA29CC /* Debug */,
513 | 5130CC921DCEEC5300FA29CC /* Release */,
514 | );
515 | defaultConfigurationIsVisible = 0;
516 | defaultConfigurationName = Release;
517 | };
518 | /* End XCConfigurationList section */
519 | };
520 | rootObject = 5130CC5A1DCEEC5200FA29CC /* Project object */;
521 | }
522 |
--------------------------------------------------------------------------------