├── IntegralImage.xcodeproj └── project.pbxproj ├── IntegralImage ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── IntegralImage.metal ├── IntegralImage.swift ├── TestClass.swift └── ViewController.swift ├── IntegralImageTests ├── Info.plist └── IntegralImageTests.swift ├── LICENSE.md └── README.md /IntegralImage.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9D5F2C311D24101A00FF50D8 /* TestClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D5F2C301D24101A00FF50D8 /* TestClass.swift */; }; 11 | 9DE037D21D22FFD7007BDCE3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE037D11D22FFD7007BDCE3 /* AppDelegate.swift */; }; 12 | 9DE037D41D22FFD7007BDCE3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE037D31D22FFD7007BDCE3 /* ViewController.swift */; }; 13 | 9DE037D71D22FFD7007BDCE3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DE037D51D22FFD7007BDCE3 /* Main.storyboard */; }; 14 | 9DE037D91D22FFD7007BDCE3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DE037D81D22FFD7007BDCE3 /* Assets.xcassets */; }; 15 | 9DE037DC1D22FFD7007BDCE3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DE037DA1D22FFD7007BDCE3 /* LaunchScreen.storyboard */; }; 16 | 9DE037E71D22FFD7007BDCE3 /* IntegralImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE037E61D22FFD7007BDCE3 /* IntegralImageTests.swift */; }; 17 | 9DE037F31D230001007BDCE3 /* IntegralImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE037F21D230001007BDCE3 /* IntegralImage.swift */; }; 18 | 9DE037F51D23001D007BDCE3 /* IntegralImage.metal in Sources */ = {isa = PBXBuildFile; fileRef = 9DE037F41D23001D007BDCE3 /* IntegralImage.metal */; }; 19 | 9DE037F91D231340007BDCE3 /* IntegralImage.metal in Sources */ = {isa = PBXBuildFile; fileRef = 9DE037F41D23001D007BDCE3 /* IntegralImage.metal */; }; 20 | 9DE037FA1D231342007BDCE3 /* IntegralImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE037F21D230001007BDCE3 /* IntegralImage.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 9DE037E31D22FFD7007BDCE3 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 9DE037C61D22FFD7007BDCE3 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 9DE037CD1D22FFD7007BDCE3; 29 | remoteInfo = IntegralImage; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 9D5F2C301D24101A00FF50D8 /* TestClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestClass.swift; sourceTree = ""; }; 35 | 9DE037CE1D22FFD7007BDCE3 /* IntegralImage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntegralImage.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 9DE037D11D22FFD7007BDCE3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 9DE037D31D22FFD7007BDCE3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 38 | 9DE037D61D22FFD7007BDCE3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 9DE037D81D22FFD7007BDCE3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 9DE037DB1D22FFD7007BDCE3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | 9DE037DD1D22FFD7007BDCE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 9DE037E21D22FFD7007BDCE3 /* IntegralImageTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegralImageTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 9DE037E61D22FFD7007BDCE3 /* IntegralImageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralImageTests.swift; sourceTree = ""; }; 44 | 9DE037E81D22FFD7007BDCE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 9DE037F21D230001007BDCE3 /* IntegralImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntegralImage.swift; sourceTree = ""; }; 46 | 9DE037F41D23001D007BDCE3 /* IntegralImage.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = IntegralImage.metal; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 9DE037CB1D22FFD7007BDCE3 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | 9DE037DF1D22FFD7007BDCE3 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 9DE037C51D22FFD7007BDCE3 = { 68 | isa = PBXGroup; 69 | children = ( 70 | 9DE037D01D22FFD7007BDCE3 /* IntegralImage */, 71 | 9DE037E51D22FFD7007BDCE3 /* IntegralImageTests */, 72 | 9DE037CF1D22FFD7007BDCE3 /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 9DE037CF1D22FFD7007BDCE3 /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 9DE037CE1D22FFD7007BDCE3 /* IntegralImage.app */, 80 | 9DE037E21D22FFD7007BDCE3 /* IntegralImageTests.xctest */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 9DE037D01D22FFD7007BDCE3 /* IntegralImage */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 9D5F2C301D24101A00FF50D8 /* TestClass.swift */, 89 | 9DE037F11D22FFDC007BDCE3 /* lib */, 90 | 9DE037D11D22FFD7007BDCE3 /* AppDelegate.swift */, 91 | 9DE037D31D22FFD7007BDCE3 /* ViewController.swift */, 92 | 9DE037D51D22FFD7007BDCE3 /* Main.storyboard */, 93 | 9DE037D81D22FFD7007BDCE3 /* Assets.xcassets */, 94 | 9DE037DA1D22FFD7007BDCE3 /* LaunchScreen.storyboard */, 95 | 9DE037DD1D22FFD7007BDCE3 /* Info.plist */, 96 | ); 97 | path = IntegralImage; 98 | sourceTree = ""; 99 | }; 100 | 9DE037E51D22FFD7007BDCE3 /* IntegralImageTests */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 9DE037E61D22FFD7007BDCE3 /* IntegralImageTests.swift */, 104 | 9DE037E81D22FFD7007BDCE3 /* Info.plist */, 105 | ); 106 | path = IntegralImageTests; 107 | sourceTree = ""; 108 | }; 109 | 9DE037F11D22FFDC007BDCE3 /* lib */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 9DE037F21D230001007BDCE3 /* IntegralImage.swift */, 113 | 9DE037F41D23001D007BDCE3 /* IntegralImage.metal */, 114 | ); 115 | name = lib; 116 | sourceTree = ""; 117 | }; 118 | /* End PBXGroup section */ 119 | 120 | /* Begin PBXNativeTarget section */ 121 | 9DE037CD1D22FFD7007BDCE3 /* IntegralImage */ = { 122 | isa = PBXNativeTarget; 123 | buildConfigurationList = 9DE037EB1D22FFD7007BDCE3 /* Build configuration list for PBXNativeTarget "IntegralImage" */; 124 | buildPhases = ( 125 | 9DE037CA1D22FFD7007BDCE3 /* Sources */, 126 | 9DE037CB1D22FFD7007BDCE3 /* Frameworks */, 127 | 9DE037CC1D22FFD7007BDCE3 /* Resources */, 128 | ); 129 | buildRules = ( 130 | ); 131 | dependencies = ( 132 | ); 133 | name = IntegralImage; 134 | productName = IntegralImage; 135 | productReference = 9DE037CE1D22FFD7007BDCE3 /* IntegralImage.app */; 136 | productType = "com.apple.product-type.application"; 137 | }; 138 | 9DE037E11D22FFD7007BDCE3 /* IntegralImageTests */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 9DE037EE1D22FFD7007BDCE3 /* Build configuration list for PBXNativeTarget "IntegralImageTests" */; 141 | buildPhases = ( 142 | 9DE037DE1D22FFD7007BDCE3 /* Sources */, 143 | 9DE037DF1D22FFD7007BDCE3 /* Frameworks */, 144 | 9DE037E01D22FFD7007BDCE3 /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | 9DE037E41D22FFD7007BDCE3 /* PBXTargetDependency */, 150 | ); 151 | name = IntegralImageTests; 152 | productName = IntegralImageTests; 153 | productReference = 9DE037E21D22FFD7007BDCE3 /* IntegralImageTests.xctest */; 154 | productType = "com.apple.product-type.bundle.unit-test"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | 9DE037C61D22FFD7007BDCE3 /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastSwiftUpdateCheck = 0800; 163 | LastUpgradeCheck = 0800; 164 | ORGANIZATIONNAME = "Christopher Helf"; 165 | TargetAttributes = { 166 | 9DE037CD1D22FFD7007BDCE3 = { 167 | CreatedOnToolsVersion = 8.0; 168 | DevelopmentTeam = N69Z84G43C; 169 | DevelopmentTeamName = "Christopher Helf"; 170 | ProvisioningStyle = Automatic; 171 | }; 172 | 9DE037E11D22FFD7007BDCE3 = { 173 | CreatedOnToolsVersion = 8.0; 174 | DevelopmentTeam = N69Z84G43C; 175 | DevelopmentTeamName = "Christopher Helf"; 176 | ProvisioningStyle = Automatic; 177 | TestTargetID = 9DE037CD1D22FFD7007BDCE3; 178 | }; 179 | }; 180 | }; 181 | buildConfigurationList = 9DE037C91D22FFD7007BDCE3 /* Build configuration list for PBXProject "IntegralImage" */; 182 | compatibilityVersion = "Xcode 3.2"; 183 | developmentRegion = English; 184 | hasScannedForEncodings = 0; 185 | knownRegions = ( 186 | en, 187 | Base, 188 | ); 189 | mainGroup = 9DE037C51D22FFD7007BDCE3; 190 | productRefGroup = 9DE037CF1D22FFD7007BDCE3 /* Products */; 191 | projectDirPath = ""; 192 | projectRoot = ""; 193 | targets = ( 194 | 9DE037CD1D22FFD7007BDCE3 /* IntegralImage */, 195 | 9DE037E11D22FFD7007BDCE3 /* IntegralImageTests */, 196 | ); 197 | }; 198 | /* End PBXProject section */ 199 | 200 | /* Begin PBXResourcesBuildPhase section */ 201 | 9DE037CC1D22FFD7007BDCE3 /* Resources */ = { 202 | isa = PBXResourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | 9DE037DC1D22FFD7007BDCE3 /* LaunchScreen.storyboard in Resources */, 206 | 9DE037D91D22FFD7007BDCE3 /* Assets.xcassets in Resources */, 207 | 9DE037D71D22FFD7007BDCE3 /* Main.storyboard in Resources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 9DE037E01D22FFD7007BDCE3 /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | 9DE037CA1D22FFD7007BDCE3 /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | 9DE037D41D22FFD7007BDCE3 /* ViewController.swift in Sources */, 226 | 9DE037F51D23001D007BDCE3 /* IntegralImage.metal in Sources */, 227 | 9DE037D21D22FFD7007BDCE3 /* AppDelegate.swift in Sources */, 228 | 9D5F2C311D24101A00FF50D8 /* TestClass.swift in Sources */, 229 | 9DE037F31D230001007BDCE3 /* IntegralImage.swift in Sources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | 9DE037DE1D22FFD7007BDCE3 /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | 9DE037E71D22FFD7007BDCE3 /* IntegralImageTests.swift in Sources */, 238 | 9DE037F91D231340007BDCE3 /* IntegralImage.metal in Sources */, 239 | 9DE037FA1D231342007BDCE3 /* IntegralImage.swift in Sources */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | /* End PBXSourcesBuildPhase section */ 244 | 245 | /* Begin PBXTargetDependency section */ 246 | 9DE037E41D22FFD7007BDCE3 /* PBXTargetDependency */ = { 247 | isa = PBXTargetDependency; 248 | target = 9DE037CD1D22FFD7007BDCE3 /* IntegralImage */; 249 | targetProxy = 9DE037E31D22FFD7007BDCE3 /* PBXContainerItemProxy */; 250 | }; 251 | /* End PBXTargetDependency section */ 252 | 253 | /* Begin PBXVariantGroup section */ 254 | 9DE037D51D22FFD7007BDCE3 /* Main.storyboard */ = { 255 | isa = PBXVariantGroup; 256 | children = ( 257 | 9DE037D61D22FFD7007BDCE3 /* Base */, 258 | ); 259 | name = Main.storyboard; 260 | sourceTree = ""; 261 | }; 262 | 9DE037DA1D22FFD7007BDCE3 /* LaunchScreen.storyboard */ = { 263 | isa = PBXVariantGroup; 264 | children = ( 265 | 9DE037DB1D22FFD7007BDCE3 /* Base */, 266 | ); 267 | name = LaunchScreen.storyboard; 268 | sourceTree = ""; 269 | }; 270 | /* End PBXVariantGroup section */ 271 | 272 | /* Begin XCBuildConfiguration section */ 273 | 9DE037E91D22FFD7007BDCE3 /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | ALWAYS_SEARCH_USER_PATHS = NO; 277 | CLANG_ANALYZER_NONNULL = YES; 278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 279 | CLANG_CXX_LIBRARY = "libc++"; 280 | CLANG_ENABLE_MODULES = YES; 281 | CLANG_ENABLE_OBJC_ARC = YES; 282 | CLANG_WARN_BOOL_CONVERSION = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 286 | CLANG_WARN_EMPTY_BODY = YES; 287 | CLANG_WARN_ENUM_CONVERSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_UNREACHABLE_CODE = YES; 291 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 292 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = dwarf; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_OPTIMIZATION_LEVEL = 0; 301 | GCC_PREPROCESSOR_DEFINITIONS = ( 302 | "DEBUG=1", 303 | "$(inherited)", 304 | ); 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 312 | MTL_ENABLE_DEBUG_INFO = YES; 313 | ONLY_ACTIVE_ARCH = YES; 314 | SDKROOT = iphoneos; 315 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 316 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 317 | }; 318 | name = Debug; 319 | }; 320 | 9DE037EA1D22FFD7007BDCE3 /* Release */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_NONNULL = YES; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BOOL_CONVERSION = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN_UNREACHABLE_CODE = YES; 338 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 339 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 340 | COPY_PHASE_STRIP = NO; 341 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 342 | ENABLE_NS_ASSERTIONS = NO; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | GCC_C_LANGUAGE_STANDARD = gnu99; 345 | GCC_NO_COMMON_BLOCKS = YES; 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 353 | MTL_ENABLE_DEBUG_INFO = NO; 354 | SDKROOT = iphoneos; 355 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 356 | VALIDATE_PRODUCT = YES; 357 | }; 358 | name = Release; 359 | }; 360 | 9DE037EC1D22FFD7007BDCE3 /* Debug */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 364 | INFOPLIST_FILE = IntegralImage/Info.plist; 365 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 366 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 367 | PRODUCT_BUNDLE_IDENTIFIER = com.helf.IntegralImage; 368 | PRODUCT_NAME = "$(TARGET_NAME)"; 369 | SWIFT_VERSION = 3.0; 370 | }; 371 | name = Debug; 372 | }; 373 | 9DE037ED1D22FFD7007BDCE3 /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 377 | INFOPLIST_FILE = IntegralImage/Info.plist; 378 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 379 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 380 | PRODUCT_BUNDLE_IDENTIFIER = com.helf.IntegralImage; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SWIFT_VERSION = 3.0; 383 | }; 384 | name = Release; 385 | }; 386 | 9DE037EF1D22FFD7007BDCE3 /* Debug */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | BUNDLE_LOADER = "$(TEST_HOST)"; 390 | INFOPLIST_FILE = IntegralImageTests/Info.plist; 391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 392 | PRODUCT_BUNDLE_IDENTIFIER = com.helf.IntegralImageTests; 393 | PRODUCT_NAME = "$(TARGET_NAME)"; 394 | SWIFT_VERSION = 3.0; 395 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IntegralImage.app/IntegralImage"; 396 | }; 397 | name = Debug; 398 | }; 399 | 9DE037F01D22FFD7007BDCE3 /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | BUNDLE_LOADER = "$(TEST_HOST)"; 403 | INFOPLIST_FILE = IntegralImageTests/Info.plist; 404 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 405 | PRODUCT_BUNDLE_IDENTIFIER = com.helf.IntegralImageTests; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_VERSION = 3.0; 408 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IntegralImage.app/IntegralImage"; 409 | }; 410 | name = Release; 411 | }; 412 | /* End XCBuildConfiguration section */ 413 | 414 | /* Begin XCConfigurationList section */ 415 | 9DE037C91D22FFD7007BDCE3 /* Build configuration list for PBXProject "IntegralImage" */ = { 416 | isa = XCConfigurationList; 417 | buildConfigurations = ( 418 | 9DE037E91D22FFD7007BDCE3 /* Debug */, 419 | 9DE037EA1D22FFD7007BDCE3 /* Release */, 420 | ); 421 | defaultConfigurationIsVisible = 0; 422 | defaultConfigurationName = Release; 423 | }; 424 | 9DE037EB1D22FFD7007BDCE3 /* Build configuration list for PBXNativeTarget "IntegralImage" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | 9DE037EC1D22FFD7007BDCE3 /* Debug */, 428 | 9DE037ED1D22FFD7007BDCE3 /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | 9DE037EE1D22FFD7007BDCE3 /* Build configuration list for PBXNativeTarget "IntegralImageTests" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 9DE037EF1D22FFD7007BDCE3 /* Debug */, 437 | 9DE037F01D22FFD7007BDCE3 /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | /* End XCConfigurationList section */ 443 | }; 444 | rootObject = 9DE037C61D22FFD7007BDCE3 /* Project object */; 445 | } 446 | -------------------------------------------------------------------------------- /IntegralImage/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // IntegralImage 4 | // 5 | // Created by Christopher Helf on 28.06.16. 6 | // Copyright © 2016 Christopher Helf. 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 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // 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. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // 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. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /IntegralImage/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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /IntegralImage/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 | -------------------------------------------------------------------------------- /IntegralImage/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 | -------------------------------------------------------------------------------- /IntegralImage/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 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /IntegralImage/IntegralImage.metal: -------------------------------------------------------------------------------- 1 | // 2 | // IntegralImage.metal 3 | // IntegralImage 4 | // 5 | // Created by Christopher Helf on 28.06.16. 6 | // Copyright © 2016 Christopher Helf. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | using namespace metal; 12 | 13 | // Our scan kernel function 14 | kernel void ii_scan( 15 | /* Our grayscale input texture */ 16 | texture2d input [[texture(0)]], 17 | /* The auxiliary texture holding the sums per threadgroup, 18 | specified as array here in order to make it optional */ 19 | texture2d aux [[texture(1)]], 20 | /* The output texture containing summed up values per block/row */ 21 | texture2d output [[texture(2)]], 22 | /* Our temporary threadgroup memory */ 23 | threadgroup float4 *temp [[ threadgroup(0) ]], 24 | /* Whether we want an inclusive scan */ 25 | constant bool &inclusive [[buffer(0)]], 26 | /* The current threads block id */ 27 | ushort2 blockIdx [[threadgroup_position_in_grid]], 28 | /* The current threads position within a block */ 29 | ushort2 threadIdx [[ thread_position_in_threadgroup ]] 30 | ) 31 | { 32 | // Another threadgroup memory in order to store sums of blocks (for inclusion) 33 | threadgroup float4 sums; 34 | 35 | // We will work with a fixed blocksize so we can unroll 36 | // the loops in reduction and down-sweep 37 | int BLOCKSIZE = 64; 38 | 39 | // Get current positions in the respective row 40 | int tdx = threadIdx.x; 41 | int bdx = blockIdx.x; 42 | int bdy = blockIdx.y; 43 | 44 | // Whether we have an auxiliary array 45 | bool hasAux = aux.get_width() > 1 || aux.get_height() > 1; 46 | 47 | // Get the actual width of the texuture 48 | uint width = uint(input.get_width()); 49 | uint auxWidth = 0; 50 | 51 | // If we have an auxiliary array, get its width 52 | if(hasAux) { 53 | auxWidth = uint(aux.get_width()); 54 | } 55 | 56 | // Calculate the block offsets, as well will dispatch 1/4 of the blocks only 57 | // in horizontal direction and process four values at the same time 58 | int blockOffset1 = (bdx*4)*BLOCKSIZE; 59 | int blockOffset2 = blockOffset1+BLOCKSIZE; 60 | int blockOffset3 = blockOffset2+BLOCKSIZE; 61 | int blockOffset4 = blockOffset3+BLOCKSIZE; 62 | 63 | // Calculate the actual position from where to read values 64 | uint2 pos1 = uint2(blockOffset1+tdx,bdy); 65 | uint2 pos2 = uint2(blockOffset2+tdx,bdy); 66 | uint2 pos3 = uint2(blockOffset3+tdx,bdy); 67 | uint2 pos4 = uint2(blockOffset4+tdx,bdy); 68 | 69 | // Load four values into shared memory, check 70 | // however that we are still within the texture 71 | // otherwise pad with zeros 72 | temp[tdx] = float4( 73 | pos1.x < width ? input.read(pos1).r : 0.0, 74 | pos2.x < width ? input.read(pos2).r : 0.0, 75 | pos3.x < width ? input.read(pos3).r : 0.0, 76 | pos4.x < width ? input.read(pos4).r : 0.0 77 | ); 78 | 79 | 80 | // Wait until all threads have read into shared memory 81 | threadgroup_barrier( mem_flags::mem_threadgroup ); 82 | 83 | // Reduction - unrolled loop 32, 16, 8, 4, 2, 1 84 | int offset = 1; 85 | int d, ai, bi; 86 | 87 | d = 32; 88 | threadgroup_barrier( mem_flags::mem_threadgroup ); 89 | if (tdx < d) { 90 | ai = offset*(2*tdx+1)-1; 91 | bi = offset*(2*tdx+2)-1; 92 | temp[bi] += temp[ai]; 93 | } 94 | offset *= 2; 95 | 96 | d = 16; 97 | threadgroup_barrier( mem_flags::mem_threadgroup ); 98 | if (tdx < d) { 99 | ai = offset*(2*tdx+1)-1; 100 | bi = offset*(2*tdx+2)-1; 101 | temp[bi] += temp[ai]; 102 | } 103 | offset *= 2; 104 | 105 | d = 8; 106 | threadgroup_barrier( mem_flags::mem_threadgroup ); 107 | if (tdx < d) { 108 | ai = offset*(2*tdx+1)-1; 109 | bi = offset*(2*tdx+2)-1; 110 | temp[bi] += temp[ai]; 111 | } 112 | offset *= 2; 113 | 114 | d = 4; 115 | threadgroup_barrier( mem_flags::mem_threadgroup ); 116 | if (tdx < d) { 117 | ai = offset*(2*tdx+1)-1; 118 | bi = offset*(2*tdx+2)-1; 119 | temp[bi] += temp[ai]; 120 | } 121 | offset *= 2; 122 | 123 | d = 2; 124 | threadgroup_barrier( mem_flags::mem_threadgroup ); 125 | if (tdx < d) { 126 | ai = offset*(2*tdx+1)-1; 127 | bi = offset*(2*tdx+2)-1; 128 | temp[bi] += temp[ai]; 129 | } 130 | offset *= 2; 131 | 132 | d = 1; 133 | threadgroup_barrier( mem_flags::mem_threadgroup ); 134 | if (tdx < d) { 135 | ai = offset*(2*tdx+1)-1; 136 | bi = offset*(2*tdx+2)-1; 137 | temp[bi] += temp[ai]; 138 | } 139 | offset *= 2; 140 | 141 | // We use four threads to store auxiliary sums (TODO) 142 | if (tdx==0) { 143 | 144 | // Store the sum 145 | sums = temp[BLOCKSIZE-1]; 146 | 147 | // Store auxiliary sums if necessary 148 | if(hasAux) { 149 | 150 | // Get the positions 151 | uint2 auxPos1 = uint2(bdx*4, bdy); 152 | uint2 auxPos2 = uint2(bdx*4+1, bdy); 153 | uint2 auxPos3 = uint2(bdx*4+2, bdy); 154 | uint2 auxPos4 = uint2(bdx*4+3, bdy); 155 | 156 | // Write them if we are not exceeding 157 | if (auxPos1[0] < auxWidth) aux.write(sums[0],auxPos1); 158 | if (auxPos2[0] < auxWidth) aux.write(sums[1],auxPos2); 159 | if (auxPos3[0] < auxWidth) aux.write(sums[2],auxPos3); 160 | if (auxPos4[0] < auxWidth) aux.write(sums[3],auxPos4); 161 | 162 | } 163 | 164 | // Reset last element 165 | // Only the first thread clears the memory 166 | temp[BLOCKSIZE-1] = float4(0.0);// clear the last element 167 | 168 | } 169 | 170 | // Down-sweep phase 171 | // traverse down tree & build scan, unrolled loop 1,2,4,8,16,32 172 | float4 t; 173 | d=1; 174 | offset >>= 1; 175 | threadgroup_barrier( mem_flags::mem_threadgroup ); 176 | if(tdx < d) { 177 | ai = offset*(2*tdx+1)-1; 178 | bi = offset*(2*tdx+2)-1; 179 | t = temp[ai]; 180 | temp[ai] = temp[bi]; 181 | temp[bi] += t; 182 | } 183 | 184 | d=2; 185 | offset >>= 1; 186 | threadgroup_barrier( mem_flags::mem_threadgroup ); 187 | if(tdx < d) { 188 | ai = offset*(2*tdx+1)-1; 189 | bi = offset*(2*tdx+2)-1; 190 | t = temp[ai]; 191 | temp[ai] = temp[bi]; 192 | temp[bi] += t; 193 | } 194 | 195 | d=4; 196 | offset >>= 1; 197 | threadgroup_barrier( mem_flags::mem_threadgroup ); 198 | if(tdx < d) { 199 | ai = offset*(2*tdx+1)-1; 200 | bi = offset*(2*tdx+2)-1; 201 | t = temp[ai]; 202 | temp[ai] = temp[bi]; 203 | temp[bi] += t; 204 | } 205 | 206 | d=8; 207 | offset >>= 1; 208 | threadgroup_barrier( mem_flags::mem_threadgroup ); 209 | if(tdx < d) { 210 | ai = offset*(2*tdx+1)-1; 211 | bi = offset*(2*tdx+2)-1; 212 | t = temp[ai]; 213 | temp[ai] = temp[bi]; 214 | temp[bi] += t; 215 | } 216 | 217 | d=16; 218 | offset >>= 1; 219 | threadgroup_barrier( mem_flags::mem_threadgroup ); 220 | if(tdx < d) { 221 | ai = offset*(2*tdx+1)-1; 222 | bi = offset*(2*tdx+2)-1; 223 | t = temp[ai]; 224 | temp[ai] = temp[bi]; 225 | temp[bi] += t; 226 | } 227 | 228 | d=32; 229 | offset >>= 1; 230 | threadgroup_barrier( mem_flags::mem_threadgroup ); 231 | if(tdx < d) { 232 | ai = offset*(2*tdx+1)-1; 233 | bi = offset*(2*tdx+2)-1; 234 | t = temp[ai]; 235 | temp[ai] = temp[bi]; 236 | temp[bi] += t; 237 | } 238 | 239 | threadgroup_barrier( mem_flags::mem_threadgroup ); 240 | 241 | // Shift array one to the left, and store sums in the last column for inclusive array 242 | if (inclusive && hasAux) { 243 | if (tdx < BLOCKSIZE -1) { 244 | if (pos1.x < width) output.write(temp[tdx+1][0], pos1); 245 | if (pos2.x < width) output.write(temp[tdx+1][1], pos2); 246 | if (pos3.x < width) output.write(temp[tdx+1][2], pos3); 247 | if (pos4.x < width) output.write(temp[tdx+1][3], pos4); 248 | } else { 249 | if (pos1.x < width) output.write(sums[0], pos1); 250 | if (pos2.x < width) output.write(sums[1], pos2); 251 | if (pos3.x < width) output.write(sums[2], pos3); 252 | if (pos4.x < width) output.write(sums[3], pos4); 253 | } 254 | 255 | } else { 256 | // write results to device memory if we are not exceeding 257 | if (pos1.x < width) output.write(temp[tdx][0], pos1); 258 | if (pos2.x < width) output.write(temp[tdx][1], pos2); 259 | if (pos3.x < width) output.write(temp[tdx][2], pos3); 260 | if (pos4.x < width) output.write(temp[tdx][3], pos4); 261 | } 262 | 263 | 264 | } 265 | 266 | // Simple kernel function to add sums from the auxiliary array 267 | kernel void ii_fixup( 268 | /* the input texture from the scan */ 269 | texture2d input [[texture(0)]], 270 | /* the auxiliary texture containing the cumulated sums */ 271 | texture2d aux [[texture(1)]], 272 | /* the output texture */ 273 | texture2d output [[texture(2)]], 274 | /* The current threads block id */ 275 | uint2 blockIdx [[threadgroup_position_in_grid]], 276 | /* the current threads global positon in the texture */ 277 | uint2 globalIdx [[thread_position_in_grid]] 278 | ) 279 | { 280 | // Get the width 281 | uint width = output.get_width(); 282 | 283 | // Get the input value 284 | float val = input.read(globalIdx).r; 285 | 286 | // Sum up the values 287 | if(globalIdx.x integralImage, int row, int col, int rows, int cols); 293 | float ii_boxintegral_f(texture2d integralImage, int row, int col, int rows, int cols) { 294 | int width = integralImage.get_width(); 295 | int height = integralImage.get_height(); 296 | int r1 = min(row-1, height-1); 297 | int c1 = min(col-1, width-1); 298 | int r2 = min(row+rows, height-1); 299 | int c2 = min(col+cols, width-1); 300 | float A(0.0f), B(0.0f), C(0.0f), D(0.0f); 301 | if (r1 >= 0 && c1 >= 0) A = integralImage.read(uint2(c1,r1)).r; 302 | if (r1 >= 0 && c2 >= 0) B = integralImage.read(uint2(c2,r1)).r; 303 | if (r2 >= 0 && c1 >= 0) C = integralImage.read(uint2(c1,r2)).r; 304 | if (r2 >= 0 && c2 >= 0) D = integralImage.read(uint2(c2,r2)).r; 305 | return max(0.f, A-B-C+D); 306 | } 307 | 308 | // Simple kernel function to calculate the sum of pixels using the integral image 309 | kernel void ii_boxintegral( 310 | texture2d integralImage [[texture(0)]], 311 | constant int &row [[buffer(0)]], 312 | constant int &col [[buffer(1)]], 313 | constant int &rows [[buffer(2)]], 314 | constant int &cols [[buffer(3)]], 315 | device float &result [[buffer(4)]] 316 | ) { 317 | result = ii_boxintegral_f(integralImage, row, col, rows, cols); 318 | } 319 | 320 | 321 | -------------------------------------------------------------------------------- /IntegralImage/IntegralImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntegralImage.swift 3 | // IntegralImage 4 | // 5 | // Created by Christopher Helf on 28.06.16. 6 | // Copyright © 2016 Christopher Helf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Metal 11 | import MetalPerformanceShaders 12 | 13 | class IntegralImage : MPSKernel { 14 | 15 | // Some general variables needed 16 | private var width : Int 17 | private var height: Int 18 | private var library : MTLLibrary 19 | 20 | // The inclusive variable, automatically adjusts the respective buffer 21 | var inclusive : Bool! { 22 | didSet { 23 | let numBytes = MemoryLayout.size 24 | self.inclusiveBuffer = device.makeBuffer(bytes: &inclusive, length: numBytes, options: []) 25 | } 26 | } 27 | var inclusiveBuffer: MTLBuffer! = nil 28 | 29 | // Fixed blocksize 30 | private let blockSize : Int = 64 31 | 32 | // The pipelines we use 33 | private var scanPipeline : MTLComputePipelineState! = nil 34 | private var fixupPipeline : MTLComputePipelineState! = nil 35 | private var boxintegralPipeline : MTLComputePipelineState! = nil 36 | private var transposePass : MPSImageTranspose! = nil 37 | 38 | // Our intermediary textures 39 | private var aux : MTLTexture! = nil 40 | private var fakeAux : MTLTexture! = nil 41 | private var auxScanned : MTLTexture! = nil 42 | private var intermediary : MTLTexture! = nil 43 | private var out : MTLTexture! = nil 44 | private var input_t : MTLTexture! = nil 45 | private var aux_t : MTLTexture! = nil 46 | private var auxScanned_t : MTLTexture! = nil 47 | private var intermediary_t : MTLTexture! = nil 48 | private var out_t : MTLTexture! = nil 49 | 50 | 51 | /** 52 | Constructor 53 | takes the MTLLibrary already as argument for the sake of simplicity, and 54 | width and height as dimensions of the be processed input texture as allocating 55 | intermediary textures is expensive, thus this should not be done too often 56 | however, upon encoding to the commandbuffer another check if performed and textures 57 | are reallocated with the correct sizes if necessary. the inclusive parameter specifies 58 | whether the resulting integral image should contain I(x,y) at (x,y) 59 | */ 60 | init(device: MTLDevice, library: MTLLibrary, width: Int, height: Int, inclusive: Bool=false) { 61 | 62 | // Store width and height 63 | self.width = width 64 | self.height = height 65 | self.library = library 66 | 67 | super.init(device: device) 68 | 69 | // Set inclusion 70 | self.setInclusive(inclusive: inclusive) 71 | 72 | // Setup 73 | setupPipelines() 74 | setupTransposePass() 75 | createIntermediaryTextures() 76 | } 77 | 78 | required convenience init?(coder aDecoder: NSCoder) { 79 | fatalError("init(coder:) has not been implemented") 80 | } 81 | 82 | // Convenience function 83 | func setInclusive(inclusive: Bool) { 84 | self.inclusive = inclusive 85 | } 86 | 87 | // Sets the pipelines for encoding 88 | func setupPipelines() { 89 | scanPipeline = self.getPipeline(kernel: "ii_scan") 90 | fixupPipeline = self.getPipeline(kernel: "ii_fixup") 91 | boxintegralPipeline = self.getPipeline(kernel: "ii_boxintegral") 92 | } 93 | 94 | // We use the MPSImageTranspose shader 95 | func setupTransposePass() { 96 | transposePass = MPSImageTranspose(device: device) 97 | } 98 | 99 | // Creates all necessary intermediary textures 100 | func createIntermediaryTextures() { 101 | 102 | let auxBlockSize = width % self.blockSize == 0 ? max(width/self.blockSize,1) : width/self.blockSize+1 103 | let auxTBlockSize = height % self.blockSize == 0 ? max(height/self.blockSize,1) : height/self.blockSize+1 104 | 105 | // There must be better way to do this without that many allocations? 106 | fakeAux = self.createIntermediaryTexture(format: .r32Float, width: 1, height: 1) 107 | aux = self.createIntermediaryTexture(format: .r32Float, width: auxBlockSize, height: height) 108 | auxScanned = self.createIntermediaryTexture(format: .r32Float, width: auxBlockSize, height: height) 109 | intermediary = self.createIntermediaryTexture(format: .r32Float, width: width, height: height) 110 | out = self.createIntermediaryTexture(format: .r32Float, width: width, height: height) 111 | input_t = self.createIntermediaryTexture(format: .r32Float, width: height, height: width) 112 | aux_t = self.createIntermediaryTexture(format: .r32Float, width: auxTBlockSize, height: width) 113 | auxScanned_t = self.createIntermediaryTexture(format: .r32Float, width: auxTBlockSize, height: width) 114 | intermediary_t = self.createIntermediaryTexture(format: .r32Float, width: height, height: width) 115 | out_t = self.createIntermediaryTexture(format: .r32Float, width: height, height: width) 116 | } 117 | 118 | // Encodes a scan pass, i.e. scan sums within blocks 119 | // if aux is nil, we use a dummy aux texture with width and height 1 120 | private func encodeScan(commandBuffer: MTLCommandBuffer, input: MTLTexture, aux: MTLTexture?, output: MTLTexture) { 121 | 122 | // Get the total number of blocks 123 | let totalBlocks = input.width % blockSize == 0 ? max(input.width/blockSize,1) : input.width/blockSize+1 124 | 125 | // Get the required number of blocks 126 | let requiredBlocks = totalBlocks % 4 == 0 ? totalBlocks/4 : totalBlocks/4+1 127 | 128 | // We are scanning per row only, i.e. multiple one-row threadgroups 129 | let scanGrid = MTLSizeMake(requiredBlocks, input.height, 1) 130 | let scanBlock = MTLSizeMake(blockSize, 1, 1) 131 | 132 | let enc = commandBuffer.makeComputeCommandEncoder() 133 | enc.pushDebugGroup("integral_image_scan") 134 | enc.setComputePipelineState(scanPipeline) 135 | 136 | // Set the input 137 | enc.setTexture(input, at: 0) 138 | 139 | // Set the auxiliary texture (nil if we dont need it) 140 | if let _aux = aux { 141 | enc.setTexture(_aux, at: 1) 142 | } else { 143 | enc.setTexture(fakeAux, at: 1) 144 | } 145 | 146 | // Set the output 147 | enc.setTexture(output, at: 2) 148 | enc.setBuffer(self.inclusiveBuffer, offset: 0, at: 0) 149 | 150 | // Create the threadgroup memory (times 4 because we use float4s) 151 | enc.setThreadgroupMemoryLength(blockSize * MemoryLayout.size * 4, at: 0) 152 | 153 | enc.dispatchThreadgroups(scanGrid, threadsPerThreadgroup: scanBlock) 154 | enc.popDebugGroup() 155 | enc.endEncoding() 156 | } 157 | 158 | // Encodes the pass that writes auxiliary sums to the rows 159 | private func encodeFixup(commandBuffer: MTLCommandBuffer, input: MTLTexture, aux: MTLTexture, output: MTLTexture) { 160 | let blocks = input.width % blockSize == 0 ? max(input.width/blockSize,1) : input.width/blockSize+1 161 | let scanBlock = MTLSizeMake(blockSize, 1, 1) 162 | let scanGrid = MTLSizeMake(blocks, input.height, 1) 163 | let enc = commandBuffer.makeComputeCommandEncoder() 164 | enc.pushDebugGroup("integral_image_fixup") 165 | enc.setComputePipelineState(fixupPipeline) 166 | enc.setTexture(input, at: 0) 167 | enc.setTexture(aux, at: 1) 168 | enc.setTexture(output, at: 2) 169 | enc.dispatchThreadgroups(scanGrid, threadsPerThreadgroup: scanBlock) 170 | enc.popDebugGroup() 171 | enc.endEncoding() 172 | } 173 | 174 | 175 | // Encodes the calculation of the integral image of sourceTexture to the commandBuffer, resulting 176 | // in the integral image in destinationTexture 177 | func encodeToCommandBuffer(_ commandBuffer: MTLCommandBuffer, 178 | sourceTexture: MTLTexture, 179 | destinationTexture: MTLTexture) { 180 | 181 | // We need a grayscale texture in any case 182 | assert(sourceTexture.pixelFormat == MTLPixelFormat.r32Float, "Source texture must be a grayscale r32Float texture") 183 | 184 | // Check whether the dimensions are the same 185 | assert(sourceTexture.width == destinationTexture.width, "Source texture must be the same size as destination texture") 186 | 187 | // Check whether the dimensions are the same 188 | assert(sourceTexture.height == destinationTexture.height, "Source texture must be the same size as destination texture") 189 | 190 | // We need at least two rows, otherwise we could get mixed up with our dummy auxiliary array 191 | assert(sourceTexture.height > 1, "Source texture must be at least of height 2") 192 | 193 | // We use a fixed blockSize of 64 and only one auxiliary array which is scanned only once, 194 | // hence this last scan pass of the aux array must fit into a single block 195 | assert(sourceTexture.height <= Int(pow(Float(blockSize), 2.0)) && sourceTexture.width <= Int(pow(Float(blockSize), 2.0))) 196 | 197 | // Check whether width and height are still valid and recreate intermediary textures if necessary 198 | if (sourceTexture.width != width || sourceTexture.height != height) { 199 | createIntermediaryTextures() 200 | } 201 | 202 | self.encodeScan(commandBuffer: commandBuffer, input: sourceTexture, aux: aux, output: intermediary) 203 | self.encodeScan(commandBuffer: commandBuffer, input: aux, aux: nil, output: auxScanned) 204 | self.encodeFixup(commandBuffer: commandBuffer, input: intermediary, aux: auxScanned, output: out) 205 | self.transposePass.encode(commandBuffer: commandBuffer, sourceTexture: out, destinationTexture: input_t) 206 | self.encodeScan(commandBuffer: commandBuffer, input: input_t, aux: aux_t, output: intermediary_t) 207 | self.encodeScan(commandBuffer: commandBuffer, input: aux_t, aux: nil, output: auxScanned_t) 208 | self.encodeFixup(commandBuffer: commandBuffer, input: intermediary_t, aux: auxScanned_t, output: out_t) 209 | self.transposePass.encode(commandBuffer: commandBuffer, sourceTexture: out_t, destinationTexture: destinationTexture) 210 | } 211 | 212 | // Convenience function for testing 213 | func getBoxIntegral(_ commandBuffer: MTLCommandBuffer, integralImage: MTLTexture, row: Int, col: Int, rows: Int, cols: Int, output: MTLBuffer) { 214 | let enc = commandBuffer.makeComputeCommandEncoder() 215 | enc.setComputePipelineState(boxintegralPipeline) 216 | enc.setTexture(integralImage, at: 0) 217 | enc.setBuffer(getBufferFromInt(row), offset: 0, at: 0) 218 | enc.setBuffer(getBufferFromInt(col), offset: 0, at: 1) 219 | enc.setBuffer(getBufferFromInt(rows), offset: 0, at: 2) 220 | enc.setBuffer(getBufferFromInt(cols), offset: 0, at: 3) 221 | enc.setBuffer(output, offset: 0, at: 4) 222 | enc.dispatchThreadgroups(MTLSize(width: 1,height: 1,depth: 1), threadsPerThreadgroup: MTLSize(width: 1,height: 1,depth: 1)) 223 | enc.endEncoding() 224 | } 225 | 226 | // Helper functions 227 | 228 | // Creates a MTLTexture from arguments 229 | private func createIntermediaryTexture(format: MTLPixelFormat, width: Int, height: Int) -> MTLTexture { 230 | let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: format, width: width, height: height, mipmapped: false) 231 | descriptor.resourceOptions = MTLResourceOptions.storageModeShared 232 | descriptor.storageMode = MTLStorageMode.shared 233 | descriptor.usage = [MTLTextureUsage.renderTarget, MTLTextureUsage.shaderRead, MTLTextureUsage.shaderWrite] 234 | return device.makeTexture(descriptor: descriptor) 235 | } 236 | 237 | // Creates a compute pipeline from a kernel name 238 | private func getPipeline(kernel: String) -> MTLComputePipelineState { 239 | let kernelFunction = library.makeFunction(name: kernel) 240 | do { 241 | let pipeline = try device.makeComputePipelineState(function: kernelFunction!) 242 | return pipeline 243 | } 244 | catch { 245 | fatalError("MMPSIntegralImage failed for kernel: \(kernel)") 246 | } 247 | } 248 | 249 | // Returns a buffer from an integer 250 | private func getBufferFromInt(_ val: Int) -> MTLBuffer { 251 | var _v = val 252 | return device.makeBuffer(bytes: &_v, length: MemoryLayout.size, options: MTLResourceOptions.storageModeShared) 253 | } 254 | 255 | 256 | 257 | } 258 | -------------------------------------------------------------------------------- /IntegralImage/TestClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestClass.swift 3 | // IntegralImage 4 | // 5 | // Created by Christopher Helf on 29.06.16. 6 | // Copyright © 2016 Christopher Helf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Metal 11 | import MetalPerformanceShaders 12 | 13 | class TestClass { 14 | 15 | var device: MTLDevice! = nil 16 | var library : MTLLibrary! = nil 17 | var commandQueue : MTLCommandQueue! = nil 18 | 19 | init() { 20 | device = MTLCreateSystemDefaultDevice()!; 21 | library = device.newDefaultLibrary()!; 22 | commandQueue = device.makeCommandQueue() 23 | } 24 | 25 | func testSmallTextureSum() -> Bool { 26 | let n = 32 27 | let width = n 28 | let height = n 29 | let (ii, input, output) = createTestSetup(width, height) 30 | let sum = TestClass.getBufferForFloat(device: device) 31 | 32 | let commandBuffer = commandQueue.makeCommandBuffer() 33 | ii.encodeToCommandBuffer(commandBuffer, sourceTexture: input, destinationTexture: output) 34 | ii.getBoxIntegral(commandBuffer, integralImage: output, row: 0, col: 0, rows: n, cols: n, output: sum) 35 | commandBuffer.commit() 36 | commandBuffer.waitUntilCompleted() 37 | let vals = TestClass.textureToArray(texture: output) 38 | return (vals[n*n-1]==Float(n*n)) && (TestClass.floatBufferToFloat(sum) == Float(n*n)) 39 | } 40 | 41 | func testMPS() -> Bool { 42 | print("testMPS") 43 | 44 | let mps = MPSImageIntegral(device: device) 45 | mps.offset = MPSOffset(x: 0, y: 0, z: 0) 46 | let n = 1000 47 | let (_, input, output) = createTestSetup(1280, 720) 48 | 49 | var elapsedGPU : UInt64 = 0 50 | for _ in 0.. Bool { 73 | 74 | let width = 1280 75 | let height = 720 76 | 77 | let mps = MPSImageIntegral(device: device) 78 | mps.offset = MPSOffset(x: 0, y: 0, z: 0) 79 | let input = TestClass.createRandomTexture(device: device, width: width, height: height) 80 | let (ii, _, output1) = createTestSetup(width, height) 81 | let (_, _, output2) = createTestSetup(width, height) 82 | 83 | let commandBuffer = commandQueue.makeCommandBuffer() 84 | mps.encode(commandBuffer: commandBuffer, sourceTexture: input, destinationTexture: output1) 85 | ii.encodeToCommandBuffer(commandBuffer, sourceTexture: input, destinationTexture: output2) 86 | commandBuffer.commit() 87 | commandBuffer.waitUntilCompleted() 88 | 89 | let vals1 = TestClass.textureToArray(texture: output1) 90 | let vals2 = TestClass.textureToArray(texture: output2) 91 | 92 | for i in 0.. Bool { 101 | 102 | let width = 1280 103 | let height = 720 104 | 105 | let mps = MPSImageIntegral(device: device) 106 | mps.offset = MPSOffset(x: 0, y: 0, z: 0) 107 | let input = TestClass.createTestTexture(device: device, val: 1.0, width: width, height: height) 108 | let (ii, _, output1) = createTestSetup(width, height) 109 | let (_, _, output2) = createTestSetup(width, height) 110 | 111 | let commandBuffer = commandQueue.makeCommandBuffer() 112 | mps.encode(commandBuffer: commandBuffer, sourceTexture: input, destinationTexture: output1) 113 | ii.encodeToCommandBuffer(commandBuffer, sourceTexture: input, destinationTexture: output2) 114 | commandBuffer.commit() 115 | commandBuffer.waitUntilCompleted() 116 | 117 | let vals1 = TestClass.textureToArray(texture: output1) 118 | let vals2 = TestClass.textureToArray(texture: output2) 119 | 120 | for i in 0..= val2 - bound && val1 <= val2 + bound) 130 | } 131 | 132 | 133 | func testTimes720p() -> Bool { 134 | print("testTimes720p") 135 | 136 | let n = 1000 137 | let (ii, input, output) = createTestSetup(1280, 720) 138 | 139 | var elapsedGPU : UInt64 = 0 140 | for _ in 0.. Bool { 162 | print("testTimes1080p") 163 | 164 | let n = 1000 165 | let (ii, input, output) = createTestSetup(1920, 1080) 166 | 167 | var elapsedGPU : UInt64 = 0 168 | for _ in 0.. (IntegralImage, MTLTexture, MTLTexture) { 190 | let ii = IntegralImage(device: device, library: library, width: width, height: height, inclusive: true) 191 | let input = TestClass.createTestTexture(device: device, val: val, width: width, height: height, useIncrease: false) 192 | let output = TestClass.createTestTexture(device: device, val: 0.0, width: width, height: height) 193 | return (ii, input, output) 194 | } 195 | 196 | class func createRandomTexture(device: MTLDevice, width: Int, height: Int) -> MTLTexture { 197 | var test = [Float](repeatElement(0.0, count: width*height)) 198 | for i in 0...size 206 | let region = MTLRegionMake2D(0, 0, texture.width, texture.height) 207 | var vals = [Float](repeatElement(0.0, count: texture.width*texture.height)) 208 | texture.getBytes(&vals, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0) 209 | for y in 0..0) || x == texture.width-1) && displayBlockSize { 219 | rowStr += "||| (row: \(y), block: \(blockCnt))\n" 220 | blockCnt+=1; 221 | } 222 | } 223 | print(rowStr) 224 | } 225 | } 226 | 227 | class func createTestTexture(device: MTLDevice, val: Float, width: Int, height: Int, useIncrease: Bool = false) -> MTLTexture { 228 | 229 | var test = [Float](repeatElement(val, count: width*height)) 230 | if useIncrease { 231 | var cnt = 1; 232 | for i in 0.. MTLTexture { 245 | let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: format, width: width, height: height, mipmapped: false) 246 | descriptor.resourceOptions = MTLResourceOptions.storageModeShared 247 | descriptor.storageMode = MTLStorageMode.shared 248 | descriptor.usage = [MTLTextureUsage.renderTarget, MTLTextureUsage.shaderRead, MTLTextureUsage.shaderWrite] 249 | let t = device.makeTexture(descriptor: descriptor) 250 | t.replace(region: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0, withBytes: bytes, bytesPerRow: width*4) 251 | return t 252 | } 253 | 254 | class func textureToArray(texture: MTLTexture) -> [Float] { 255 | let bytesPerRow = texture.width*MemoryLayout.size 256 | let region = MTLRegionMake2D(0, 0, texture.width, texture.height) 257 | var vals = [Float](repeatElement(0.0, count: texture.width*texture.height)) 258 | texture.getBytes(&vals, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0) 259 | return vals; 260 | } 261 | 262 | class func getBufferForFloat(device: MTLDevice) -> MTLBuffer { 263 | return device.makeBuffer(length: MemoryLayout.size, options: MTLResourceOptions.storageModeShared) 264 | } 265 | 266 | class func floatBufferToFloat(_ buffer: MTLBuffer) -> Float { 267 | let data = NSData(bytesNoCopy: buffer.contents(), 268 | length: MemoryLayout.size, freeWhenDone: false) 269 | var rtn : Float = -1.0 270 | data.getBytes(&rtn, length:MemoryLayout.size) 271 | return rtn 272 | } 273 | 274 | 275 | } 276 | -------------------------------------------------------------------------------- /IntegralImage/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // IntegralImage 4 | // 5 | // Created by Christopher Helf on 28.06.16. 6 | // Copyright © 2016 Christopher Helf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | let tests = TestClass() 18 | assert(tests.testSmallTextureSum() == true) 19 | assert(tests.compareImplAgainstMPSWithBounds() == true) 20 | assert(tests.compareImplAgainstMPS() == true) 21 | assert(tests.testMPS() == true) 22 | assert(tests.testTimes720p() == true) 23 | assert(tests.testTimes1080p() == true) 24 | 25 | 26 | //printTexture(texture: output) 27 | 28 | print("Tests Completed") 29 | } 30 | 31 | override func didReceiveMemoryWarning() { 32 | super.didReceiveMemoryWarning() 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /IntegralImageTests/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 | -------------------------------------------------------------------------------- /IntegralImageTests/IntegralImageTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntegralImageTests.swift 3 | // IntegralImageTests 4 | // 5 | // Created by Christopher Helf on 28.06.16. 6 | // Copyright © 2016 Christopher Helf. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Metal 11 | import MetalPerformanceShaders 12 | 13 | @testable import IntegralImage 14 | 15 | class IntegralImageTests: XCTestCase { 16 | 17 | var device: MTLDevice! = nil 18 | var library : MTLLibrary! = nil 19 | var commandQueue : MTLCommandQueue! = nil 20 | 21 | override func setUp() { 22 | super.setUp() 23 | device = MTLCreateSystemDefaultDevice()!; 24 | library = device.newDefaultLibrary()!; 25 | commandQueue = device.newCommandQueue() 26 | } 27 | 28 | func testSmallTexture() { 29 | 30 | let width = 16 31 | let height = 16 32 | 33 | let ii = IntegralImage(device: device, library: library, width: width, height: height) 34 | let input = createTestTexture(device: device, val: 1.0, width: width, height: height) 35 | let output = createTestTexture(device: device, val: 0.0, width: width, height: height) 36 | 37 | let commandBuffer = commandQueue.commandBuffer() 38 | ii.encodeToCommandBuffer(commandBuffer, sourceTexture: input, destinationTexture: output) 39 | commandBuffer.commit() 40 | commandBuffer.waitUntilCompleted() 41 | 42 | print(textureToArray(texture: output)) 43 | 44 | XCTAssertTrue(1==1) 45 | 46 | } 47 | 48 | func testPerformanceExample() { 49 | // This is an example of a performance test case. 50 | 51 | } 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Christopher Helf 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Integral-Image in Metal for iOS 2 | 3 | This repository contains an iOS project written in Swift 3.0 where I've tried to recreate a [parallel prefix sum algorithm](http://http.developer.nvidia.com/GPUGems3/gpugems3_ch39.html) written in Metal for educational purposes and then compare against validity and performance of the [MPSImageIntegral](https://developer.apple.com/reference/metalperformanceshaders/mpsimageintegral) class provided by Apple. Given a first attempt it's not even that bad (around ~70 FPS on a 720p image with my implementation as compared to ~260 FPS with MPSImageIntegral on a iPhone 6S). I've tried to optimize if by fixing the algorithm to a specific block size, unrolling all loops and using `float4` values in each thread in order to save global memory bandwith. 4 | 5 | The `TestClass`file gives an idea on how to use the `IntegralImage` class. It's basically instantiating the class via 6 | 7 | IntegralImage(device: MTLDevice, library: MTLLibrary, width: Int, height: Int, inclusive: Bool) 8 | 9 | where `inclusive` indicates whether the computed sums at `(x,y)` should include `I(x,y)`. 10 | 11 | Feel free to experiment and check out the source. The repository is not maintained and I don't guarantee validity (even though all tests are passing just fine). --------------------------------------------------------------------------------