├── .gitignore ├── MagicPaint.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── MagicPaint ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── GameViewController.swift ├── Info.plist ├── MagicPaint-Bridging-Header.h ├── MetalRenderer.swift ├── MetalUtils.swift ├── MetalView.swift └── Shaders.metal ├── MagicPaintTests ├── Info.plist └── MagicPaintTests.swift ├── MagicPaintUITests ├── Info.plist └── MagicPaintUITests.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | -------------------------------------------------------------------------------- /MagicPaint.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 460D8C451BBDDB2000AE7F91 /* MetalUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 460D8C441BBDDB2000AE7F91 /* MetalUtils.swift */; settings = {ASSET_TAGS = (); }; }; 11 | 469C081D1BBB14B900C1BB24 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469C081C1BBB14B900C1BB24 /* AppDelegate.swift */; }; 12 | 469C081F1BBB14B900C1BB24 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469C081E1BBB14B900C1BB24 /* GameViewController.swift */; }; 13 | 469C08211BBB14B900C1BB24 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 469C08201BBB14B900C1BB24 /* Shaders.metal */; }; 14 | 469C08241BBB14B900C1BB24 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 469C08221BBB14B900C1BB24 /* Main.storyboard */; }; 15 | 469C08261BBB14B900C1BB24 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 469C08251BBB14B900C1BB24 /* Assets.xcassets */; }; 16 | 469C08291BBB14B900C1BB24 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 469C08271BBB14B900C1BB24 /* LaunchScreen.storyboard */; }; 17 | 469C08341BBB14B900C1BB24 /* MagicPaintTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469C08331BBB14B900C1BB24 /* MagicPaintTests.swift */; }; 18 | 469C083F1BBB14B900C1BB24 /* MagicPaintUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469C083E1BBB14B900C1BB24 /* MagicPaintUITests.swift */; }; 19 | 469C08691BBB23EA00C1BB24 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469C08681BBB23EA00C1BB24 /* MetalView.swift */; settings = {ASSET_TAGS = (); }; }; 20 | 469C086B1BBB3F7E00C1BB24 /* MetalRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469C086A1BBB3F7E00C1BB24 /* MetalRenderer.swift */; settings = {ASSET_TAGS = (); }; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 469C08301BBB14B900C1BB24 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 469C08111BBB14B900C1BB24 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 469C08181BBB14B900C1BB24; 29 | remoteInfo = MagicPaint; 30 | }; 31 | 469C083B1BBB14B900C1BB24 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 469C08111BBB14B900C1BB24 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 469C08181BBB14B900C1BB24; 36 | remoteInfo = MagicPaint; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 460D8C441BBDDB2000AE7F91 /* MetalUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalUtils.swift; sourceTree = ""; }; 42 | 469C08191BBB14B900C1BB24 /* MagicPaint.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MagicPaint.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 469C081C1BBB14B900C1BB24 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 44 | 469C081E1BBB14B900C1BB24 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = ""; }; 45 | 469C08201BBB14B900C1BB24 /* Shaders.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = ""; }; 46 | 469C08231BBB14B900C1BB24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 469C08251BBB14B900C1BB24 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | 469C08281BBB14B900C1BB24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | 469C082A1BBB14B900C1BB24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | 469C082F1BBB14B900C1BB24 /* MagicPaintTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MagicPaintTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 469C08331BBB14B900C1BB24 /* MagicPaintTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicPaintTests.swift; sourceTree = ""; }; 52 | 469C08351BBB14B900C1BB24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 469C083A1BBB14B900C1BB24 /* MagicPaintUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MagicPaintUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 469C083E1BBB14B900C1BB24 /* MagicPaintUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicPaintUITests.swift; sourceTree = ""; }; 55 | 469C08401BBB14B900C1BB24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 469C084C1BBB14C500C1BB24 /* MagicPaint-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MagicPaint-Bridging-Header.h"; sourceTree = ""; }; 57 | 469C08681BBB23EA00C1BB24 /* MetalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = ""; }; 58 | 469C086A1BBB3F7E00C1BB24 /* MetalRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalRenderer.swift; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 469C08161BBB14B900C1BB24 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 469C082C1BBB14B900C1BB24 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | 469C08371BBB14B900C1BB24 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXFrameworksBuildPhase section */ 84 | 85 | /* Begin PBXGroup section */ 86 | 469C08101BBB14B900C1BB24 = { 87 | isa = PBXGroup; 88 | children = ( 89 | 469C081B1BBB14B900C1BB24 /* MagicPaint */, 90 | 469C08321BBB14B900C1BB24 /* MagicPaintTests */, 91 | 469C083D1BBB14B900C1BB24 /* MagicPaintUITests */, 92 | 469C081A1BBB14B900C1BB24 /* Products */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | 469C081A1BBB14B900C1BB24 /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 469C08191BBB14B900C1BB24 /* MagicPaint.app */, 100 | 469C082F1BBB14B900C1BB24 /* MagicPaintTests.xctest */, 101 | 469C083A1BBB14B900C1BB24 /* MagicPaintUITests.xctest */, 102 | ); 103 | name = Products; 104 | sourceTree = ""; 105 | }; 106 | 469C081B1BBB14B900C1BB24 /* MagicPaint */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 469C081C1BBB14B900C1BB24 /* AppDelegate.swift */, 110 | 469C081E1BBB14B900C1BB24 /* GameViewController.swift */, 111 | 469C08681BBB23EA00C1BB24 /* MetalView.swift */, 112 | 469C086A1BBB3F7E00C1BB24 /* MetalRenderer.swift */, 113 | 469C08221BBB14B900C1BB24 /* Main.storyboard */, 114 | 469C08201BBB14B900C1BB24 /* Shaders.metal */, 115 | 460D8C441BBDDB2000AE7F91 /* MetalUtils.swift */, 116 | 469C08251BBB14B900C1BB24 /* Assets.xcassets */, 117 | 469C08271BBB14B900C1BB24 /* LaunchScreen.storyboard */, 118 | 469C082A1BBB14B900C1BB24 /* Info.plist */, 119 | 469C084C1BBB14C500C1BB24 /* MagicPaint-Bridging-Header.h */, 120 | ); 121 | path = MagicPaint; 122 | sourceTree = ""; 123 | }; 124 | 469C08321BBB14B900C1BB24 /* MagicPaintTests */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 469C08331BBB14B900C1BB24 /* MagicPaintTests.swift */, 128 | 469C08351BBB14B900C1BB24 /* Info.plist */, 129 | ); 130 | path = MagicPaintTests; 131 | sourceTree = ""; 132 | }; 133 | 469C083D1BBB14B900C1BB24 /* MagicPaintUITests */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 469C083E1BBB14B900C1BB24 /* MagicPaintUITests.swift */, 137 | 469C08401BBB14B900C1BB24 /* Info.plist */, 138 | ); 139 | path = MagicPaintUITests; 140 | sourceTree = ""; 141 | }; 142 | /* End PBXGroup section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | 469C08181BBB14B900C1BB24 /* MagicPaint */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = 469C08431BBB14B900C1BB24 /* Build configuration list for PBXNativeTarget "MagicPaint" */; 148 | buildPhases = ( 149 | 469C08151BBB14B900C1BB24 /* Sources */, 150 | 469C08161BBB14B900C1BB24 /* Frameworks */, 151 | 469C08171BBB14B900C1BB24 /* Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = MagicPaint; 158 | productName = MagicPaint; 159 | productReference = 469C08191BBB14B900C1BB24 /* MagicPaint.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | 469C082E1BBB14B900C1BB24 /* MagicPaintTests */ = { 163 | isa = PBXNativeTarget; 164 | buildConfigurationList = 469C08461BBB14B900C1BB24 /* Build configuration list for PBXNativeTarget "MagicPaintTests" */; 165 | buildPhases = ( 166 | 469C082B1BBB14B900C1BB24 /* Sources */, 167 | 469C082C1BBB14B900C1BB24 /* Frameworks */, 168 | 469C082D1BBB14B900C1BB24 /* Resources */, 169 | ); 170 | buildRules = ( 171 | ); 172 | dependencies = ( 173 | 469C08311BBB14B900C1BB24 /* PBXTargetDependency */, 174 | ); 175 | name = MagicPaintTests; 176 | productName = MagicPaintTests; 177 | productReference = 469C082F1BBB14B900C1BB24 /* MagicPaintTests.xctest */; 178 | productType = "com.apple.product-type.bundle.unit-test"; 179 | }; 180 | 469C08391BBB14B900C1BB24 /* MagicPaintUITests */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = 469C08491BBB14B900C1BB24 /* Build configuration list for PBXNativeTarget "MagicPaintUITests" */; 183 | buildPhases = ( 184 | 469C08361BBB14B900C1BB24 /* Sources */, 185 | 469C08371BBB14B900C1BB24 /* Frameworks */, 186 | 469C08381BBB14B900C1BB24 /* Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | 469C083C1BBB14B900C1BB24 /* PBXTargetDependency */, 192 | ); 193 | name = MagicPaintUITests; 194 | productName = MagicPaintUITests; 195 | productReference = 469C083A1BBB14B900C1BB24 /* MagicPaintUITests.xctest */; 196 | productType = "com.apple.product-type.bundle.ui-testing"; 197 | }; 198 | /* End PBXNativeTarget section */ 199 | 200 | /* Begin PBXProject section */ 201 | 469C08111BBB14B900C1BB24 /* Project object */ = { 202 | isa = PBXProject; 203 | attributes = { 204 | LastSwiftUpdateCheck = 0700; 205 | LastUpgradeCheck = 0700; 206 | ORGANIZATIONNAME = "Carl Wieland"; 207 | TargetAttributes = { 208 | 469C08181BBB14B900C1BB24 = { 209 | CreatedOnToolsVersion = 7.0; 210 | }; 211 | 469C082E1BBB14B900C1BB24 = { 212 | CreatedOnToolsVersion = 7.0; 213 | TestTargetID = 469C08181BBB14B900C1BB24; 214 | }; 215 | 469C08391BBB14B900C1BB24 = { 216 | CreatedOnToolsVersion = 7.0; 217 | TestTargetID = 469C08181BBB14B900C1BB24; 218 | }; 219 | }; 220 | }; 221 | buildConfigurationList = 469C08141BBB14B900C1BB24 /* Build configuration list for PBXProject "MagicPaint" */; 222 | compatibilityVersion = "Xcode 3.2"; 223 | developmentRegion = English; 224 | hasScannedForEncodings = 0; 225 | knownRegions = ( 226 | en, 227 | Base, 228 | ); 229 | mainGroup = 469C08101BBB14B900C1BB24; 230 | productRefGroup = 469C081A1BBB14B900C1BB24 /* Products */; 231 | projectDirPath = ""; 232 | projectRoot = ""; 233 | targets = ( 234 | 469C08181BBB14B900C1BB24 /* MagicPaint */, 235 | 469C082E1BBB14B900C1BB24 /* MagicPaintTests */, 236 | 469C08391BBB14B900C1BB24 /* MagicPaintUITests */, 237 | ); 238 | }; 239 | /* End PBXProject section */ 240 | 241 | /* Begin PBXResourcesBuildPhase section */ 242 | 469C08171BBB14B900C1BB24 /* Resources */ = { 243 | isa = PBXResourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 469C08291BBB14B900C1BB24 /* LaunchScreen.storyboard in Resources */, 247 | 469C08261BBB14B900C1BB24 /* Assets.xcassets in Resources */, 248 | 469C08241BBB14B900C1BB24 /* Main.storyboard in Resources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | 469C082D1BBB14B900C1BB24 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 469C08381BBB14B900C1BB24 /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXResourcesBuildPhase section */ 267 | 268 | /* Begin PBXSourcesBuildPhase section */ 269 | 469C08151BBB14B900C1BB24 /* Sources */ = { 270 | isa = PBXSourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | 469C08211BBB14B900C1BB24 /* Shaders.metal in Sources */, 274 | 469C086B1BBB3F7E00C1BB24 /* MetalRenderer.swift in Sources */, 275 | 469C081F1BBB14B900C1BB24 /* GameViewController.swift in Sources */, 276 | 469C08691BBB23EA00C1BB24 /* MetalView.swift in Sources */, 277 | 469C081D1BBB14B900C1BB24 /* AppDelegate.swift in Sources */, 278 | 460D8C451BBDDB2000AE7F91 /* MetalUtils.swift in Sources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | 469C082B1BBB14B900C1BB24 /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | 469C08341BBB14B900C1BB24 /* MagicPaintTests.swift in Sources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | 469C08361BBB14B900C1BB24 /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 469C083F1BBB14B900C1BB24 /* MagicPaintUITests.swift in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXTargetDependency section */ 301 | 469C08311BBB14B900C1BB24 /* PBXTargetDependency */ = { 302 | isa = PBXTargetDependency; 303 | target = 469C08181BBB14B900C1BB24 /* MagicPaint */; 304 | targetProxy = 469C08301BBB14B900C1BB24 /* PBXContainerItemProxy */; 305 | }; 306 | 469C083C1BBB14B900C1BB24 /* PBXTargetDependency */ = { 307 | isa = PBXTargetDependency; 308 | target = 469C08181BBB14B900C1BB24 /* MagicPaint */; 309 | targetProxy = 469C083B1BBB14B900C1BB24 /* PBXContainerItemProxy */; 310 | }; 311 | /* End PBXTargetDependency section */ 312 | 313 | /* Begin PBXVariantGroup section */ 314 | 469C08221BBB14B900C1BB24 /* Main.storyboard */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | 469C08231BBB14B900C1BB24 /* Base */, 318 | ); 319 | name = Main.storyboard; 320 | sourceTree = ""; 321 | }; 322 | 469C08271BBB14B900C1BB24 /* LaunchScreen.storyboard */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 469C08281BBB14B900C1BB24 /* Base */, 326 | ); 327 | name = LaunchScreen.storyboard; 328 | sourceTree = ""; 329 | }; 330 | /* End PBXVariantGroup section */ 331 | 332 | /* Begin XCBuildConfiguration section */ 333 | 469C08411BBB14B900C1BB24 /* Debug */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BOOL_CONVERSION = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INT_CONVERSION = YES; 347 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 351 | COPY_PHASE_STRIP = NO; 352 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | ENABLE_TESTABILITY = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu99; 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | GCC_OPTIMIZATION_LEVEL = 0; 359 | GCC_PREPROCESSOR_DEFINITIONS = ( 360 | "DEBUG=1", 361 | "$(inherited)", 362 | ); 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 370 | MTL_ENABLE_DEBUG_INFO = YES; 371 | ONLY_ACTIVE_ARCH = YES; 372 | SDKROOT = iphoneos; 373 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 374 | TARGETED_DEVICE_FAMILY = "1,2"; 375 | }; 376 | name = Debug; 377 | }; 378 | 469C08421BBB14B900C1BB24 /* Release */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ALWAYS_SEARCH_USER_PATHS = NO; 382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 383 | CLANG_CXX_LIBRARY = "libc++"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_CONSTANT_CONVERSION = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN_UNREACHABLE_CODE = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu99; 401 | GCC_NO_COMMON_BLOCKS = YES; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 409 | MTL_ENABLE_DEBUG_INFO = NO; 410 | SDKROOT = iphoneos; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | VALIDATE_PRODUCT = YES; 413 | }; 414 | name = Release; 415 | }; 416 | 469C08441BBB14B900C1BB24 /* Debug */ = { 417 | isa = XCBuildConfiguration; 418 | buildSettings = { 419 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 420 | CLANG_ENABLE_MODULES = YES; 421 | INFOPLIST_FILE = MagicPaint/Info.plist; 422 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 423 | PRODUCT_BUNDLE_IDENTIFIER = com.balanceoni.MagicPaint; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | SWIFT_OBJC_BRIDGING_HEADER = "MagicPaint/MagicPaint-Bridging-Header.h"; 426 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 427 | }; 428 | name = Debug; 429 | }; 430 | 469C08451BBB14B900C1BB24 /* Release */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 434 | CLANG_ENABLE_MODULES = YES; 435 | INFOPLIST_FILE = MagicPaint/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = com.balanceoni.MagicPaint; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_OBJC_BRIDGING_HEADER = "MagicPaint/MagicPaint-Bridging-Header.h"; 440 | }; 441 | name = Release; 442 | }; 443 | 469C08471BBB14B900C1BB24 /* Debug */ = { 444 | isa = XCBuildConfiguration; 445 | buildSettings = { 446 | BUNDLE_LOADER = "$(TEST_HOST)"; 447 | DEBUG_INFORMATION_FORMAT = dwarf; 448 | INFOPLIST_FILE = MagicPaintTests/Info.plist; 449 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 450 | PRODUCT_BUNDLE_IDENTIFIER = com.balanceoni.MagicPaintTests; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MagicPaint.app/MagicPaint"; 453 | }; 454 | name = Debug; 455 | }; 456 | 469C08481BBB14B900C1BB24 /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | BUNDLE_LOADER = "$(TEST_HOST)"; 460 | INFOPLIST_FILE = MagicPaintTests/Info.plist; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 462 | PRODUCT_BUNDLE_IDENTIFIER = com.balanceoni.MagicPaintTests; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MagicPaint.app/MagicPaint"; 465 | }; 466 | name = Release; 467 | }; 468 | 469C084A1BBB14B900C1BB24 /* Debug */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | DEBUG_INFORMATION_FORMAT = dwarf; 472 | INFOPLIST_FILE = MagicPaintUITests/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 474 | PRODUCT_BUNDLE_IDENTIFIER = com.balanceoni.MagicPaintUITests; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | TEST_TARGET_NAME = MagicPaint; 477 | USES_XCTRUNNER = YES; 478 | }; 479 | name = Debug; 480 | }; 481 | 469C084B1BBB14B900C1BB24 /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | INFOPLIST_FILE = MagicPaintUITests/Info.plist; 485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.balanceoni.MagicPaintUITests; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | TEST_TARGET_NAME = MagicPaint; 489 | USES_XCTRUNNER = YES; 490 | }; 491 | name = Release; 492 | }; 493 | /* End XCBuildConfiguration section */ 494 | 495 | /* Begin XCConfigurationList section */ 496 | 469C08141BBB14B900C1BB24 /* Build configuration list for PBXProject "MagicPaint" */ = { 497 | isa = XCConfigurationList; 498 | buildConfigurations = ( 499 | 469C08411BBB14B900C1BB24 /* Debug */, 500 | 469C08421BBB14B900C1BB24 /* Release */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 469C08431BBB14B900C1BB24 /* Build configuration list for PBXNativeTarget "MagicPaint" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 469C08441BBB14B900C1BB24 /* Debug */, 509 | 469C08451BBB14B900C1BB24 /* Release */, 510 | ); 511 | defaultConfigurationIsVisible = 0; 512 | defaultConfigurationName = Release; 513 | }; 514 | 469C08461BBB14B900C1BB24 /* Build configuration list for PBXNativeTarget "MagicPaintTests" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 469C08471BBB14B900C1BB24 /* Debug */, 518 | 469C08481BBB14B900C1BB24 /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | 469C08491BBB14B900C1BB24 /* Build configuration list for PBXNativeTarget "MagicPaintUITests" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | 469C084A1BBB14B900C1BB24 /* Debug */, 527 | 469C084B1BBB14B900C1BB24 /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | /* End XCConfigurationList section */ 533 | }; 534 | rootObject = 469C08111BBB14B900C1BB24 /* Project object */; 535 | } 536 | -------------------------------------------------------------------------------- /MagicPaint.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MagicPaint/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MagicPaint 4 | // 5 | // Created by Carl Wieland on 9/29/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | 21 | 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(application: UIApplication) { 26 | // 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. 27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 28 | } 29 | 30 | func applicationDidEnterBackground(application: UIApplication) { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | func applicationWillEnterForeground(application: UIApplication) { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | func applicationDidBecomeActive(application: UIApplication) { 40 | // 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. 41 | } 42 | 43 | func applicationWillTerminate(application: UIApplication) { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /MagicPaint/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 | } -------------------------------------------------------------------------------- /MagicPaint/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 | -------------------------------------------------------------------------------- /MagicPaint/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 | -------------------------------------------------------------------------------- /MagicPaint/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // MagicPaint 4 | // 5 | // Created by Carl Wieland on 9/29/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Metal 11 | import MetalKit 12 | 13 | 14 | class GameViewController:UIViewController { 15 | weak var delegate:GameViewControllerDelegate? = nil 16 | var timeSinceLastDraw:NSTimeInterval = 0 17 | // What vsync refresh interval to fire at. (Sets CADisplayLink frameinterval property) 18 | // set to 1 by default, which is the CADisplayLink default setting (60 FPS). 19 | // Setting to 2, will cause gameloop to trigger every other vsync (throttling to 30 FPS) 20 | var interval = 1 21 | // Used to pause and resume the controller. 22 | var paused:Bool{ 23 | get{ 24 | return gameLoopPaused 25 | } 26 | set{ 27 | if(gameLoopPaused == newValue) 28 | { 29 | return; 30 | } 31 | 32 | if(self.timer != nil) 33 | { 34 | // inform the delegate we are about to pause 35 | self.delegate?.viewController(self, 36 | willPause:newValue); 37 | 38 | if(newValue == true) 39 | { 40 | gameLoopPaused = newValue; 41 | timer?.paused = true 42 | 43 | // ask the view to release textures until its resumed 44 | (self.view as? MetalView)?.releaseTextures(); 45 | } 46 | else 47 | { 48 | gameLoopPaused = newValue; 49 | timer?.paused = false; 50 | } 51 | } 52 | 53 | } 54 | } 55 | 56 | var timer:CADisplayLink? = nil 57 | 58 | var firstDrawOccurred = false 59 | var timeSinceLastDrawPreviousTime:CFTimeInterval = 0 60 | var gameLoopPaused = false 61 | let renderer = MetalRenderer() 62 | 63 | 64 | 65 | 66 | 67 | override func viewDidLoad() { 68 | super.viewDidLoad() 69 | if let view = self.view as? MetalView{ 70 | view.delegate = renderer 71 | renderer.configure(view) 72 | } 73 | self.delegate = renderer 74 | interval = 1 75 | NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("didEnterBackground:"), name: UIApplicationDidEnterBackgroundNotification, object: nil) 76 | NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("willEnterForground:"), name: UIApplicationWillEnterForegroundNotification, object: nil) 77 | 78 | } 79 | 80 | deinit{ 81 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil) 82 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationWillEnterForegroundNotification, object: nil) 83 | } 84 | 85 | // used to fire off the main game loop 86 | func dispatchGameLoop(){ 87 | timer = UIScreen.mainScreen().displayLinkWithTarget(self, selector: Selector("gameloop")) 88 | timer?.frameInterval = self.interval 89 | timer?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) 90 | } 91 | 92 | // the main game loop called by the timer above 93 | func gameloop(){ 94 | 95 | // tell our delegate to update itself here. 96 | self.delegate?.update(self) 97 | 98 | if(!firstDrawOccurred){ 99 | // set up timing data for display since this is the first time through this loop 100 | timeSinceLastDraw = 0.0; 101 | timeSinceLastDrawPreviousTime = CACurrentMediaTime(); 102 | firstDrawOccurred = true; 103 | } 104 | else 105 | { 106 | // figure out the time since we last we drew 107 | let currentTime = CACurrentMediaTime(); 108 | 109 | timeSinceLastDraw = currentTime - timeSinceLastDrawPreviousTime; 110 | 111 | // keep track of the time interval between draws 112 | timeSinceLastDrawPreviousTime = currentTime; 113 | } 114 | 115 | // display (render) 116 | assert(self.view.isKindOfClass(MetalView.classForCoder())); 117 | 118 | // call the display method directly on the render view (setNeedsDisplay: has been disabled in the renderview by default) 119 | (self.view as! MetalView).display(); 120 | } 121 | 122 | // use invalidates the main game loop. when the app is set to terminate 123 | func stopGameLoop(){ 124 | timer?.invalidate() 125 | } 126 | 127 | func didEnterBackground(notification:NSNotification){ 128 | self.paused = true 129 | } 130 | func willEnterForground(notification:NSNotification){ 131 | self.paused = false 132 | } 133 | override func viewWillAppear(animated: Bool) { 134 | super.viewWillAppear(animated) 135 | self.dispatchGameLoop() 136 | } 137 | override func viewWillDisappear(animated: Bool) { 138 | super.viewWillDisappear(animated) 139 | self.stopGameLoop() 140 | } 141 | 142 | 143 | 144 | } 145 | 146 | 147 | protocol GameViewControllerDelegate:NSObjectProtocol{ 148 | func update(controller:GameViewController)->Void 149 | func viewController(controller:GameViewController, willPause pause:Bool) 150 | 151 | 152 | } -------------------------------------------------------------------------------- /MagicPaint/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | metal 33 | 34 | UIStatusBarHidden 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /MagicPaint/MagicPaint-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /MagicPaint/MetalRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalRenderer.swift 3 | // MagicPaint 4 | // 5 | // Created by Carl Wieland on 9/29/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import Metal 12 | import MetalPerformanceShaders 13 | 14 | let kMaxBufferBytesPerFrame = 1024*1024; 15 | let kInFlightCommandBuffers = 3; 16 | let vertexData:[Float] = 17 | [ 18 | -1.0, -1.0, 0.0, 1.0, 19 | -1.0, 1.0, 0.0, 1.0, 20 | 1.0, -1.0, 0.0, 1.0, 21 | 22 | 1.0, -1.0, 0.0, 1.0, 23 | -1.0, 1.0, 0.0, 1.0, 24 | 1.0, 1.0, 0.0, 1.0, 25 | 26 | ] 27 | 28 | let uvData:[Float] = 29 | [ 30 | 0, 0, 31 | 0, 1, 32 | 1, 0, 33 | 1, 0, 34 | 0, 1, 35 | 1, 1, 36 | ] 37 | 38 | 39 | 40 | 41 | class MetalRenderer: NSObject,GameViewControllerDelegate,MetalViewDelegate, AVCaptureVideoDataOutputSampleBufferDelegate { 42 | var device:MTLDevice! 43 | var commandQueue:MTLCommandQueue! 44 | var defaultLibrary:MTLLibrary! 45 | var inflight_semaphore:dispatch_semaphore_t = dispatch_semaphore_create(kInFlightCommandBuffers); 46 | var constantDataBufferIndex = 0; 47 | 48 | var dynamicUniformBuffer = [MTLBuffer]() 49 | 50 | // render stage 51 | var pipelineState:MTLRenderPipelineState!; 52 | var vertexBuffer:MTLBuffer!; 53 | var uvBuffer:MTLBuffer!; 54 | var labColorBuffer:MTLBuffer!; 55 | 56 | 57 | var depthState:MTLDepthStencilState!; 58 | var sampler:MTLSamplerState! 59 | 60 | // Video texture 61 | let captureSession = AVCaptureSession(); 62 | var videoTextureCache:CVMetalTextureCacheRef!; 63 | var videoTexture = [MTLTexture?](count:3,repeatedValue:nil) 64 | 65 | 66 | func configure(view:MetalView){ 67 | self.device = view.device 68 | // setup view with drawable formats 69 | view.depthPixelFormat = MTLPixelFormat.Depth32Float; 70 | view.stencilPixelFormat = MTLPixelFormat.Invalid; 71 | view.sampleCount = 1 72 | 73 | // create a new command queue 74 | self.commandQueue = self.device.newCommandQueue(); 75 | 76 | self.defaultLibrary = self.device.newDefaultLibrary(); 77 | if(self.defaultLibrary == nil) { 78 | NSLog(">> ERROR: Couldnt create a default shader library"); 79 | // assert here becuase if the shader libary isn't loading, nothing good will happen 80 | assert(false,"false"); 81 | } 82 | 83 | // allocate one region of memory for the constant buffer 84 | for i in 0..(pData) 138 | vData[0] = LAB.l 139 | vData[1] = LAB.a 140 | vData[2] = LAB.b 141 | } 142 | 143 | func update(controller:GameViewController)->Void{ 144 | 145 | // vData is pointer to the MTLBuffer's Float data contents 146 | let pData = vertexBuffer.contents() 147 | let vData = UnsafeMutablePointer(pData + 256 * constantDataBufferIndex) 148 | 149 | // reset the vertices to default before adding animated offsets 150 | vData.initializeFrom(vertexData) 151 | 152 | let uData = uvBuffer.contents() 153 | let mutUV = UnsafeMutablePointer(uData + 256 * constantDataBufferIndex) 154 | 155 | // reset the vertices to default before adding animated offsets 156 | mutUV.initializeFrom(uvData) 157 | 158 | } 159 | 160 | 161 | func viewController(controller:GameViewController, willPause pause:Bool){ 162 | if(pause){ 163 | 164 | } 165 | else{ 166 | 167 | } 168 | } 169 | 170 | // called if the view changes orientation or size, renderer can precompute its view and projection matricies here for example 171 | func reshape(view:MetalView)->Void{ 172 | 173 | } 174 | 175 | 176 | func blurTexture(inout inTexture:MTLTexture, blurRadius:Float, q:MTLCommandQueue) 177 | { 178 | // Create the usual Metal objects. 179 | // MPS does not need a dedicated MTLCommandBuffer or MTLComputeCommandEncoder. 180 | // This is a trivial example. You should reuse the MTL objects you already have, if you have them. 181 | let device = q.device; 182 | let buffer = q.commandBuffer(); 183 | let allocator = { (filter:MPSKernel, cmdBuf:MTLCommandBuffer, sourceTexture:MTLTexture) -> MTLTexture in 184 | let format = sourceTexture.pixelFormat 185 | let d = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(format, width: sourceTexture.width, height: sourceTexture.height, mipmapped: false) 186 | let result = cmdBuf.device.newTextureWithDescriptor(d) 187 | return result 188 | } 189 | 190 | // Create a MPS filter. 191 | let blur = MPSImageGaussianBlur(device: device, sigma: blurRadius) 192 | // Defaults are okay here for other MPSKernel properties (clipRect, origin, edgeMode). 193 | 194 | // Attempt to do the work in place. Since we provided a copyAllocator as an out-of-place 195 | // fallback, we don’t need to check to see if it succeeded or not. 196 | // See the "Minimal MPSCopyAllocator Implementation" code listing for a sample myAllocator. 197 | var mutable = inTexture as MTLTexture?; 198 | 199 | blur.encodeToCommandBuffer(buffer, inPlaceTexture: &mutable, fallbackCopyAllocator: allocator) 200 | buffer.commit() 201 | 202 | // The usual Metal enqueue process. 203 | buffer.waitUntilCompleted(); 204 | 205 | } 206 | 207 | 208 | 209 | 210 | // delegate should perform all rendering here 211 | func render(view:MetalView)->Void{ 212 | // Allow the renderer to preflight 3 frames on the CPU (using a semapore as a guard) and commit them to the GPU. 213 | // This semaphore will get signaled once the GPU completes a frame's work via addCompletedHandler callback below, 214 | // signifying the CPU can go ahead and prepare another frame. 215 | dispatch_semaphore_wait(inflight_semaphore, DISPATCH_TIME_FOREVER); 216 | 217 | // Prior to sending any data to the GPU, constant buffers should be updated accordingly on the CPU. 218 | // [self updateConstantBuffer]; 219 | 220 | // create a new command buffer for each renderpass to the current drawable 221 | let commandBuffer = self.commandQueue.commandBuffer(); 222 | 223 | // create a render command encoder so we can render into something 224 | 225 | if let renderPassDescriptor = view.renderPassDescriptor, drawable = view.currentDrawable, texture = self.videoTexture[constantDataBufferIndex]{ 226 | 227 | let renderEncoder = commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor); 228 | renderEncoder.setDepthStencilState(depthState) 229 | 230 | 231 | renderEncoder.pushDebugGroup("screen") 232 | renderEncoder.setRenderPipelineState(pipelineState) 233 | renderEncoder.setVertexBuffer(vertexBuffer, offset: 256*constantDataBufferIndex, atIndex: 0) 234 | renderEncoder.setVertexBuffer(uvBuffer, offset: 256*constantDataBufferIndex, atIndex: 1) 235 | renderEncoder.setVertexBuffer(labColorBuffer, offset:0, atIndex: 2) 236 | 237 | renderEncoder.setFragmentSamplerState(self.sampler, atIndex: 0) 238 | // var toBlur = texture 239 | // blurTexture(&toBlur, blurRadius: 2, q: self.commandQueue) 240 | renderEncoder.setFragmentTexture(texture, atIndex: 0) 241 | 242 | renderEncoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1) 243 | 244 | renderEncoder.popDebugGroup() 245 | renderEncoder.endEncoding(); 246 | 247 | // schedule a present once the framebuffer is complete 248 | commandBuffer.presentDrawable(drawable); 249 | } 250 | 251 | // Add a completion handler / block to be called once the command buffer is completed by the GPU. All completion handlers will be returned in the order they were committed. 252 | let block_sema = inflight_semaphore; 253 | commandBuffer.addCompletedHandler { (buffer:MTLCommandBuffer) -> Void in 254 | // GPU has completed rendering the frame and is done using the contents of any buffers previously encoded on the CPU for that frame. 255 | // Signal the semaphore and allow the CPU to proceed and construct the next frame. 256 | dispatch_semaphore_signal(block_sema); 257 | } 258 | 259 | // finalize rendering here. this will push the command buffer to the GPU 260 | commandBuffer.commit(); 261 | 262 | // This index represents the current portion of the ring buffer being used for a given frame's constant buffer updates. 263 | // Once the CPU has completed updating a shared CPU/GPU memory buffer region for a frame, this index should be updated so the 264 | // next portion of the ring buffer can be written by the CPU. Note, this should only be done *after* all writes to any 265 | // buffers requiring synchronization for a given frame is done in order to avoid writing a region of the ring buffer that the GPU may be reading. 266 | constantDataBufferIndex = (constantDataBufferIndex + 1) % kInFlightCommandBuffers; 267 | 268 | } 269 | 270 | 271 | 272 | func setupVideoCapture(){ 273 | 274 | var texCache = Unmanaged?() 275 | 276 | let status = CVMetalTextureCacheCreate(kCFAllocatorDefault,nil, self.device, nil, &texCache) 277 | if status == kCVReturnSuccess{ 278 | videoTextureCache = texCache!.takeRetainedValue() 279 | } 280 | else{ 281 | assert(false,">> ERROR: Couldnt create a texture cache"); 282 | } 283 | 284 | self.captureSession.beginConfiguration() 285 | self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; 286 | 287 | // Get the a video device with preference to the front facing camera 288 | var videoDevice:AVCaptureDevice! = nil 289 | let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo); 290 | for device in devices 291 | { 292 | if (device.position == AVCaptureDevicePosition.Back) 293 | { 294 | videoDevice = device as? AVCaptureDevice; 295 | break; 296 | } 297 | } 298 | if(videoDevice == nil){ 299 | videoDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo); 300 | } 301 | 302 | if(videoDevice == nil){ 303 | assert(false,">> ERROR: Couldnt create a AVCaptureDevice"); 304 | } 305 | 306 | do{ 307 | // Device input 308 | let deviceInput = try AVCaptureDeviceInput(device: videoDevice) 309 | 310 | captureSession.addInput(deviceInput); 311 | 312 | // Create the output for the capture session. 313 | let dataOutput = AVCaptureVideoDataOutput() 314 | dataOutput.alwaysDiscardsLateVideoFrames = true 315 | let settings = //[String(kCVPixelBufferPixelFormatTypeKey):Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)]; 316 | [(kCVPixelBufferPixelFormatTypeKey as NSString):Int(kCVPixelFormatType_32BGRA)] as [NSObject : AnyObject]; 317 | // Set the color space. 318 | dataOutput.videoSettings = settings 319 | 320 | // Set dispatch to be on the main thread to create the texture in memory and allow Metal to use it for rendering 321 | dataOutput.setSampleBufferDelegate(self, queue:dispatch_get_main_queue()); 322 | 323 | captureSession.addOutput(dataOutput); 324 | captureSession.commitConfiguration() 325 | // this will trigger capture on its own queue 326 | captureSession.startRunning(); 327 | } 328 | catch{ 329 | assert(false,">> ERROR: Couldnt create AVCaptureDeviceInput:\(error)"); 330 | } 331 | } 332 | 333 | func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) { 334 | // if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer){ 335 | // 336 | // var textureY:MTLTexture? = nil; 337 | // var textureCbCr:MTLTexture? = nil; 338 | // 339 | // let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); 340 | // var textureRef: Unmanaged? 341 | // var status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, videoTextureCache, pixelBuffer,nil, MTLPixelFormat.R8Unorm, CVPixelBufferGetWidthOfPlane(pixelBuffer, 0), height, 0, &textureRef); 342 | // if let tex = textureRef where status == kCVReturnSuccess 343 | // { 344 | // textureY = CVMetalTextureGetTexture(tex.takeRetainedValue()); 345 | // } 346 | // 347 | // 348 | // // textureCbCr 349 | // status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, videoTextureCache, pixelBuffer, nil, MTLPixelFormat.RG8Unorm, CVPixelBufferGetWidthOfPlane(pixelBuffer, 1), CVPixelBufferGetHeightOfPlane(pixelBuffer, 1), 1, &textureRef); 350 | // if let tex = textureRef where status == kCVReturnSuccess 351 | // { 352 | // textureCbCr = CVMetalTextureGetTexture(tex.takeRetainedValue()); 353 | // } 354 | // if let texY = textureY, texCbCr = textureCbCr{ 355 | // let doubleProvider = TwoTextureProvider(tex1: texY, tex2: texCbCr) 356 | // let textConversionFilter = TextureColorConversion(provider: doubleProvider, context: context) 357 | // self.videoTexture[constantDataBufferIndex] = textConversionFilter.texture; 358 | // 359 | //// if let image = UIImage.image(textConversionFilter.texture){ 360 | //// print("image:\(image)") 361 | //// } 362 | // } 363 | // } 364 | if let sourceImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer){ 365 | let width = CVPixelBufferGetWidth(sourceImageBuffer); 366 | let height = CVPixelBufferGetHeight(sourceImageBuffer); 367 | 368 | 369 | var textureRef: Unmanaged? 370 | let status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, videoTextureCache, sourceImageBuffer,nil, MTLPixelFormat.BGRA8Unorm, width, height, 0, &textureRef); 371 | if let tex = textureRef, texture = CVMetalTextureGetTexture(tex.takeRetainedValue()) where status == kCVReturnSuccess 372 | { 373 | self.videoTexture[constantDataBufferIndex] = texture; 374 | } 375 | else{ 376 | assert(false,">> ERROR: Couldn't get texture from texture ref"); 377 | } 378 | 379 | } 380 | } 381 | 382 | } 383 | -------------------------------------------------------------------------------- /MagicPaint/MetalUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalProtocols.swift 3 | // RAV 4 | // 5 | // Created by Carl Wieland on 7/7/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | import UIKit 9 | import Metal 10 | 11 | protocol MetalTextureProvider:class{ 12 | var texture:MTLTexture{get} 13 | } 14 | 15 | protocol MetalTextureConsumer:class{ 16 | var provider:MetalTextureProvider?{get} 17 | } 18 | 19 | //func writeImageToFile( image:CGImageRef, path:String)->Bool { 20 | // let url = NSURL(fileURLWithPath: path); 21 | // guard let destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, nil) else{ 22 | // NSLog("Failed to create CGImageDestination for \(path)"); 23 | // return false; 24 | // } 25 | // 26 | // CGImageDestinationAddImage(destination, image, nil); 27 | // 28 | // if (!CGImageDestinationFinalize(destination)) { 29 | // NSLog("Failed to write image to \(path)"); 30 | // 31 | // return false; 32 | // } 33 | // 34 | // return true; 35 | //} 36 | 37 | func rgbToLAB(r:Float, g:Float, b:Float)->(l:Float,a:Float,b:Float){ 38 | var var_R = r; 39 | var var_G = g; 40 | var var_B = b; 41 | 42 | if ( var_R > 0.04045 ){ 43 | var_R = pow(( ( var_R + 0.055 ) / 1.055 ), 2.4); 44 | } 45 | else{ 46 | var_R = var_R / 12.92; 47 | } 48 | 49 | if ( var_G > 0.04045 ) { 50 | var_G = pow(( ( var_G + 0.055 ) / 1.055 ) , 2.4); 51 | } 52 | else{ 53 | var_G = var_G / 12.92; 54 | } 55 | if ( var_B > 0.04045 ){ 56 | var_B = pow(( ( var_B + 0.055 ) / 1.055 ), 2.4); 57 | } 58 | else{ 59 | var_B = var_B / 12.92; 60 | 61 | } 62 | var_R = var_R * 100; 63 | var_G = var_G * 100; 64 | var_B = var_B * 100; 65 | 66 | //Observer. = 2°, Illuminant = D65 67 | let X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805; 68 | let Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722; 69 | let Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505; 70 | 71 | var var_X = X / 95.047;//ref_X //ref_X = 95.047 Observer= 2°, Illuminant= D65 72 | var var_Y = Y / 100.000; //ref_Y = 100.000 73 | var var_Z = Z / 108.883; //ref_Z = 108.883 74 | 75 | if ( var_X > 0.008856 ){ 76 | var_X = pow(var_X,0.333333333333); 77 | } 78 | else{ 79 | var_X = ( 7.787 * var_X ) + ( 16 / 116 ); 80 | } 81 | if ( var_Y > 0.008856 ){ 82 | var_Y = pow(var_Y ,0.333333333333); 83 | } 84 | else{ 85 | var_Y = ( 7.787 * var_Y ) + ( 16 / 116 ); 86 | } 87 | if ( var_Z > 0.008856 ){ 88 | var_Z = pow(var_Z,0.333333333333); 89 | } 90 | else{ 91 | var_Z = ( 7.787 * var_Z ) + ( 16 / 116 ); 92 | } 93 | 94 | let CIEL = ( 116 * var_Y ) - 16; 95 | let CIEa = 500 * ( var_X - var_Y ); 96 | let CIEb = 200 * ( var_Y - var_Z ); 97 | return (CIEL,CIEa,CIEb) 98 | } 99 | 100 | extension UIImage{ 101 | static func image(texture:MTLTexture)->UIImage?{ 102 | 103 | // assert(texture.pixelFormat == MTLPixelFormat.RGBA8Unorm,"Pixel format of texture must be RGBA8Unorm to create UIImage"); 104 | 105 | let width = texture.width 106 | let height = texture.height 107 | let imageByteCount = width * height * 4; 108 | let data = NSMutableData(length: imageByteCount)! 109 | 110 | let imageBytes = UnsafeMutablePointer(data.bytes) 111 | 112 | let bytesPerRow = width * 4; 113 | let region = MTLRegionMake2D(0, 0, width, height); 114 | texture.getBytes(imageBytes, bytesPerRow:bytesPerRow, fromRegion:region,mipmapLevel:0); 115 | let provider = CGDataProviderCreateWithData(nil, data.bytes, imageByteCount,nil); 116 | let bitsPerComponent = 8; 117 | let bitsPerPixel = 32; 118 | let colorSpaceRef = CGColorSpaceCreateDeviceRGB()! 119 | let bitmapInfo = CGBitmapInfo (rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Big.rawValue); 120 | let renderingIntent = CGColorRenderingIntent.RenderingIntentDefault; 121 | if let imageRef = CGImageCreate(width, 122 | height, 123 | bitsPerComponent, 124 | bitsPerPixel, 125 | bytesPerRow, 126 | colorSpaceRef, 127 | bitmapInfo, 128 | provider, 129 | nil, 130 | false, 131 | renderingIntent){ 132 | 133 | let image = UIImage(CGImage: imageRef) 134 | // let dir = NSFileManager.defaultManager().applicationSupportDirectory().stringByAppendingString("/test.png") 135 | // print("Dir:\(dir)") 136 | // writeImageToFile(imageRef,path:dir) 137 | return image 138 | } 139 | 140 | return nil 141 | 142 | } 143 | 144 | 145 | // func fixOrientation()->UIImage { 146 | // 147 | //// // No-op if the orientation is already correct 148 | //// if (self.imageOrientation == UIImageOrientation.Up) return self; 149 | // 150 | // // We need to calculate the proper transformation to make the image upright. 151 | // // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. 152 | // var transform = CGAffineTransformIdentity; 153 | // 154 | // switch (self.imageOrientation) { 155 | // case UIImageOrientation.Down: fallthrough 156 | // case UIImageOrientation.DownMirrored: 157 | // transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height); 158 | // transform = CGAffineTransformRotate(transform, CGFloat(M_PI)); 159 | // case UIImageOrientation.Left: fallthrough 160 | // case UIImageOrientation.LeftMirrored: 161 | // transform = CGAffineTransformTranslate(transform, self.size.width, 0); 162 | // transform = CGAffineTransformRotate(transform,CGFloat(M_PI_2)); 163 | // 164 | // case UIImageOrientation.Right: fallthrough 165 | // case UIImageOrientation.RightMirrored: 166 | // transform = CGAffineTransformTranslate(transform, 0, self.size.height); 167 | // transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2)); 168 | // case UIImageOrientation.Up:fallthrough 169 | // case UIImageOrientation.UpMirrored: 170 | // break; 171 | // } 172 | // 173 | // switch (self.imageOrientation) { 174 | // case UIImageOrientation.UpMirrored:fallthrough 175 | // case UIImageOrientation.DownMirrored: 176 | // transform = CGAffineTransformTranslate(transform, self.size.width, 0); 177 | // transform = CGAffineTransformScale(transform, -1, 1); 178 | // break; 179 | // 180 | // case UIImageOrientation.LeftMirrored:fallthrough 181 | // case UIImageOrientation.RightMirrored: 182 | // transform = CGAffineTransformTranslate(transform, self.size.height, 0); 183 | // transform = CGAffineTransformScale(transform, -1, 1); 184 | // break; 185 | // case UIImageOrientation.Up:fallthrough 186 | // case UIImageOrientation.Down:fallthrough 187 | // case UIImageOrientation.Left:fallthrough 188 | // case UIImageOrientation.Right: 189 | // break; 190 | // } 191 | // 192 | // // Now we draw the underlying CGImage into a new context, applying the transform 193 | // // calculated above. 194 | // let ctx = CGBitmapContextCreate(nil, Int(self.size.width), Int(self.size.height), 195 | // CGImageGetBitsPerComponent(self.CGImage), 0, 196 | // CGImageGetColorSpace(self.CGImage)!, 197 | // CGImageGetBitmapInfo(self.CGImage).rawValue); 198 | // CGContextConcatCTM(ctx, transform); 199 | // switch (self.imageOrientation) { 200 | // case UIImageOrientation.Left:fallthrough 201 | // case UIImageOrientation.LeftMirrored:fallthrough 202 | // case UIImageOrientation.Right:fallthrough 203 | // case UIImageOrientation.RightMirrored: 204 | // // Grr... 205 | // CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage); 206 | // break; 207 | // 208 | // default: 209 | // CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage); 210 | // break; 211 | // } 212 | // 213 | // // And now we just create a new UIImage from the drawing context 214 | // let cgimg = CGBitmapContextCreateImage(ctx)! 215 | // let img = UIImage(CGImage: cgimg) 216 | // return img; 217 | // } 218 | // 219 | 220 | } -------------------------------------------------------------------------------- /MagicPaint/MetalView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalView.swift 3 | // MagicPaint 4 | // 5 | // Created by Carl Wieland on 9/29/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Metal 11 | 12 | 13 | @objc protocol MetalViewDelegate{ 14 | // called if the view changes orientation or size, renderer can precompute its view and projection matricies here for example 15 | func reshape(view:MetalView)->Void 16 | 17 | // delegate should perform all rendering here 18 | func render(view:MetalView)->Void 19 | } 20 | 21 | 22 | class MetalView: UIView { 23 | weak var delegate:MetalViewDelegate? = nil 24 | weak var _metalLayer:CAMetalLayer? = nil 25 | 26 | var _depthTex:MTLTexture? = nil 27 | var _stencilTex:MTLTexture? = nil 28 | var _msaaTex:MTLTexture? = nil 29 | var _layerSizeDidUpdate = false 30 | 31 | let device:MTLDevice! 32 | 33 | var _currentDrawable:CAMetalDrawable? = nil 34 | var currentDrawable:CAMetalDrawable?{ 35 | if(_currentDrawable == nil){ 36 | _currentDrawable = _metalLayer?.nextDrawable() 37 | } 38 | return _currentDrawable 39 | } 40 | 41 | 42 | var depthPixelFormat:MTLPixelFormat = MTLPixelFormat.Invalid 43 | var stencilPixelFormat:MTLPixelFormat = MTLPixelFormat.Invalid; 44 | var sampleCount:Int = 0 45 | 46 | 47 | override init(frame: CGRect) { 48 | self.device = MTLCreateSystemDefaultDevice() 49 | 50 | super.init(frame: frame) 51 | self.opaque = true 52 | self.backgroundColor = nil 53 | _metalLayer = self.layer as? CAMetalLayer 54 | _metalLayer?.device = self.device 55 | _metalLayer?.pixelFormat = MTLPixelFormat.BGRA8Unorm; 56 | 57 | // this is the default but if we wanted to perform compute on the final rendering layer we could set this to no 58 | _metalLayer?.framebufferOnly = true; 59 | 60 | 61 | } 62 | required init?(coder aDecoder: NSCoder) { 63 | 64 | self.device = MTLCreateSystemDefaultDevice() 65 | 66 | super.init(coder: aDecoder) 67 | 68 | self.opaque = true 69 | self.backgroundColor = nil 70 | _metalLayer = self.layer as? CAMetalLayer 71 | _metalLayer?.device = self.device 72 | _metalLayer?.pixelFormat = MTLPixelFormat.BGRA8Unorm; 73 | 74 | // this is the default but if we wanted to perform compute on the final rendering layer we could set this to no 75 | _metalLayer?.framebufferOnly = true; 76 | 77 | } 78 | static override func layerClass()->AnyClass{ 79 | return CAMetalLayer.classForCoder() 80 | } 81 | 82 | override func didMoveToWindow() { 83 | self.contentScaleFactor = (self.window?.screen.nativeScale)! 84 | } 85 | 86 | override var contentScaleFactor:CGFloat{ 87 | didSet{ 88 | self._layerSizeDidUpdate = true 89 | } 90 | } 91 | override func layoutSubviews() { 92 | super.layoutSubviews() 93 | self._layerSizeDidUpdate = true 94 | } 95 | 96 | func setupRenderPassDescriptorForTexture(texture:MTLTexture){ 97 | if(_renderPassDescriptor == nil){ 98 | _renderPassDescriptor = MTLRenderPassDescriptor() 99 | } 100 | 101 | // create a color attachment every frame since we have to recreate the texture every frame 102 | let colorAttachment = _renderPassDescriptor.colorAttachments[0]; 103 | colorAttachment.texture = texture; 104 | 105 | // make sure to clear every frame for best performance 106 | colorAttachment.loadAction = MTLLoadAction.Clear; 107 | colorAttachment.clearColor = MTLClearColorMake(0.65, 0.65, 0.65, 1.0); 108 | 109 | // if sample count is greater than 1, render into using MSAA, then resolve into our color texture 110 | if(sampleCount > 1) 111 | { 112 | let doUpdate = _msaaTex == nil || ( _msaaTex != nil && ( ( _msaaTex?.width != texture.width ) || ( _msaaTex?.height != texture.height ) || ( _msaaTex?.sampleCount != self.sampleCount ))); 113 | 114 | if(doUpdate) 115 | { 116 | let desc = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat( MTLPixelFormat.BGRA8Unorm, 117 | width: texture.width, 118 | height: texture.height, 119 | mipmapped:false); 120 | desc.textureType = MTLTextureType.Type2DMultisample 121 | 122 | // sample count was specified to the view by the renderer. 123 | // this must match the sample count given to any pipeline state using this render pass descriptor 124 | desc.sampleCount = self.sampleCount; 125 | 126 | _msaaTex = self.device?.newTextureWithDescriptor(desc); 127 | } 128 | 129 | // When multisampling, perform rendering to _msaaTex, then resolve 130 | // to 'texture' at the end of the scene 131 | colorAttachment.texture = _msaaTex; 132 | colorAttachment.resolveTexture = texture; 133 | 134 | // set store action to resolve in this case 135 | colorAttachment.storeAction = MTLStoreAction.MultisampleResolve; 136 | } 137 | else 138 | { 139 | // store only attachments that will be presented to the screen, as in this case 140 | colorAttachment.storeAction = MTLStoreAction.Store; 141 | } // color0 142 | 143 | // Now create the depth and stencil attachments 144 | 145 | if(self.depthPixelFormat != MTLPixelFormat.Invalid) 146 | { 147 | let doUpdate = _depthTex == nil || ( ( _depthTex?.width != texture.width ) || ( _depthTex?.height != texture.height ) || ( _depthTex?.sampleCount != self.sampleCount )); 148 | 149 | if( doUpdate) 150 | { 151 | // If we need a depth texture and don't have one, or if the depth texture we have is the wrong size 152 | // Then allocate one of the proper size 153 | let desc = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(depthPixelFormat, 154 | width: texture.width, 155 | height: texture.height, 156 | mipmapped: false); 157 | 158 | desc.textureType = (self.sampleCount > 1) ? MTLTextureType.Type2DMultisample : MTLTextureType.Type2D; 159 | desc.sampleCount = self.sampleCount; 160 | 161 | _depthTex = self.device?.newTextureWithDescriptor(desc); 162 | 163 | let depthAttachment = _renderPassDescriptor.depthAttachment; 164 | depthAttachment.texture = _depthTex; 165 | depthAttachment.loadAction = MTLLoadAction.Clear; 166 | depthAttachment.storeAction = MTLStoreAction.DontCare; 167 | depthAttachment.clearDepth = 1.0; 168 | } 169 | } // depth 170 | 171 | if(stencilPixelFormat != MTLPixelFormat.Invalid) 172 | { 173 | let doUpdate = _stencilTex == nil || ( ( _stencilTex?.width != texture.width ) 174 | || ( _stencilTex?.height != texture.height ) 175 | || ( _stencilTex?.sampleCount != self.sampleCount )); 176 | 177 | if(doUpdate) 178 | { 179 | // If we need a stencil texture and don't have one, or if the depth texture we have is the wrong size 180 | // Then allocate one of the proper size 181 | let desc = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(stencilPixelFormat, 182 | width: texture.width, 183 | height: texture.height, 184 | mipmapped: false); 185 | 186 | desc.textureType = (self.sampleCount > 1) ? MTLTextureType.Type2DMultisample : MTLTextureType.Type2D; 187 | desc.sampleCount = self.sampleCount; 188 | 189 | _stencilTex = self.device?.newTextureWithDescriptor(desc); 190 | 191 | let stencilAttachment = _renderPassDescriptor.stencilAttachment; 192 | stencilAttachment.texture = _stencilTex; 193 | stencilAttachment.loadAction = MTLLoadAction.Clear; 194 | stencilAttachment.storeAction = MTLStoreAction.DontCare; 195 | stencilAttachment.clearStencil = 0; 196 | } 197 | } //stencil 198 | 199 | 200 | } 201 | 202 | var _renderPassDescriptor:MTLRenderPassDescriptor! = nil 203 | var renderPassDescriptor:MTLRenderPassDescriptor?{ 204 | if let drawable = self.currentDrawable{ 205 | self.setupRenderPassDescriptorForTexture(drawable.texture) 206 | } 207 | else{ 208 | _renderPassDescriptor = nil 209 | } 210 | return _renderPassDescriptor 211 | } 212 | 213 | 214 | 215 | func display(){ 216 | // Create autorelease pool per frame to avoid possible deadlock situations 217 | // because there are 3 CAMetalDrawables sitting in an autorelease pool. 218 | 219 | autoreleasepool 220 | { 221 | // handle display changes here 222 | if(_layerSizeDidUpdate) 223 | { 224 | // set the metal layer to the drawable size in case orientation or size changes 225 | var drawableSize = self.bounds.size; 226 | drawableSize.width *= self.contentScaleFactor; 227 | drawableSize.height *= self.contentScaleFactor; 228 | 229 | _metalLayer?.drawableSize = drawableSize; 230 | 231 | // renderer delegate method so renderer can resize anything if needed 232 | self.delegate?.reshape(self); 233 | 234 | _layerSizeDidUpdate = false; 235 | } 236 | 237 | // rendering delegate method to ask renderer to draw this frame's content 238 | self.delegate?.render(self); 239 | 240 | // do not retain current drawable beyond the frame. 241 | // There should be no strong references to this object outside of this view class 242 | _currentDrawable = nil; 243 | } 244 | 245 | } 246 | 247 | func releaseTextures(){ 248 | _depthTex = nil; 249 | _stencilTex = nil 250 | _msaaTex = nil 251 | } 252 | 253 | 254 | 255 | 256 | } 257 | -------------------------------------------------------------------------------- /MagicPaint/Shaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Shaders.metal 3 | // MagicPaint 4 | // 5 | // Created by Carl Wieland on 9/29/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | using namespace metal; 12 | 13 | struct VertexInOut 14 | { 15 | float4 position [[position]]; 16 | float2 uv; 17 | float3 lab; 18 | }; 19 | 20 | vertex VertexInOut passThroughVertex(uint vid [[ vertex_id ]], 21 | constant packed_float4* position [[ buffer(0) ]], 22 | constant packed_float2* uvs [[ buffer(1) ]], 23 | constant float* labColors [[ buffer(2) ]]) 24 | { 25 | VertexInOut outVertex; 26 | 27 | outVertex.position = position[vid]; 28 | outVertex.uv = uvs[vid]; 29 | 30 | // outVertex.uv.x = 1 - outVertex.uv.x; 31 | outVertex.uv.y = 1 - outVertex.uv.y; 32 | outVertex.lab.r = labColors[0]; 33 | outVertex.lab.g = labColors[1]; 34 | outVertex.lab.b = labColors[2]; 35 | return outVertex; 36 | }; 37 | 38 | fragment half4 passThroughFragment(VertexInOut inFrag [[stage_in]], 39 | sampler texSmpl [[sampler(0)]], 40 | texture2d tex [[ texture(0) ]]) 41 | { 42 | 43 | //color in 44 | half4 rgba = half4(tex.sample(texSmpl,inFrag.uv)); 45 | half var_R = rgba.r; 46 | half var_G = rgba.g; 47 | half var_B = rgba.b; 48 | 49 | if ( var_R > 0.04045 ){ 50 | var_R = pow(( ( var_R + 0.055 ) / 1.055 ), 2.4); 51 | } 52 | else{ 53 | var_R = var_R / 12.92; 54 | } 55 | 56 | if ( var_G > 0.04045 ) { 57 | var_G = pow(( ( var_G + 0.055 ) / 1.055 ) , 2.4); 58 | } 59 | else{ 60 | var_G = var_G / 12.92; 61 | } 62 | if ( var_B > 0.04045 ){ 63 | var_B = pow(( ( var_B + 0.055 ) / 1.055 ), 2.4); 64 | } 65 | else{ 66 | var_B = var_B / 12.92; 67 | 68 | } 69 | var_R = var_R * 100; 70 | var_G = var_G * 100; 71 | var_B = var_B * 100; 72 | 73 | //Observer. = 2°, Illuminant = D65 74 | half X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805; 75 | half Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722; 76 | half Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505; 77 | 78 | half var_X = X / 95.047;//ref_X //ref_X = 95.047 Observer= 2°, Illuminant= D65 79 | half var_Y = Y / 100.000; //ref_Y = 100.000 80 | half var_Z = Z / 108.883; //ref_Z = 108.883 81 | 82 | if ( var_X > 0.008856 ){ 83 | var_X = pow(var_X,(half)0.333333333333f); 84 | } 85 | else{ 86 | var_X = ( 7.787 * var_X ) + ( 16 / 116 ); 87 | } 88 | if ( var_Y > 0.008856 ){ 89 | var_Y = pow(var_Y ,(half)0.333333333333f); 90 | } 91 | else{ 92 | var_Y = ( 7.787 * var_Y ) + ( 16 / 116 ); 93 | } 94 | if ( var_Z > 0.008856 ){ 95 | var_Z = pow(var_Z,(half)0.333333333333f); 96 | } 97 | else{ 98 | var_Z = ( 7.787 * var_Z ) + ( 16 / 116 ); 99 | } 100 | //http://www.brucelindbloom.com/index.html?Equations.html 101 | float L2 = ( 116 * var_Y ) - 16; 102 | float a2 = 500 * ( var_X - var_Y ); 103 | float b2 = 200 * ( var_Y - var_Z ); 104 | 105 | float L1 = inFrag.lab.r; 106 | float a1 = inFrag.lab.g; 107 | float b1 = inFrag.lab.b; 108 | 109 | 110 | float delL = (L1 - L2); 111 | float dela = (a1 - a2); 112 | float delb = (b1 - b2); 113 | float delta = sqrt((delL * delL) + (dela * dela) + (delb * delb) ); 114 | 115 | if(delta < 40){ 116 | // // float dif = 100.0 - delta / 100.0; 117 | // half mixr = 187.0/255.0;// * dif; 118 | // half mixg = 153.0/255.0;// * dif; 119 | // half mixb = 133.0/255.0 ;//* dif; 120 | 121 | return half4(1,1,1, 1); 122 | } 123 | else{ 124 | return rgba; 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /MagicPaintTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MagicPaintTests/MagicPaintTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MagicPaintTests.swift 3 | // MagicPaintTests 4 | // 5 | // Created by Carl Wieland on 9/29/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MagicPaint 11 | 12 | class MagicPaintTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /MagicPaintUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MagicPaintUITests/MagicPaintUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MagicPaintUITests.swift 3 | // MagicPaintUITests 4 | // 5 | // Created by Carl Wieland on 9/29/15. 6 | // Copyright © 2015 Carl Wieland. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MagicPaintUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Realtime-Camera-Magic 2 | iOS Swift Realtime video manipulation 3 | This is an example of how to get iOS Camera input into a Metal Texture and present that on screen. 4 | This may be useful to provide some shader on the camera input or other image processing. It runs at 60hz while passing through some intense shaders. Triple buffered. 5 | 6 | 7 | --------------------------------------------------------------------------------