├── PageAlignedArray.swift ├── PageAlignedArray.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── eldade.xcuserdatad │ └── xcschemes │ ├── PageAlignedArray.xcscheme │ └── xcschememanagement.plist ├── PageAlignedArray ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift └── README.md /PageAlignedArray.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Eldad Eilam 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, are 5 | // permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this list of 8 | // conditions and the following disclaimer. 9 | // 10 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | // of conditions and the following disclaimer in the documentation and/or other materials 12 | // provided with the distribution. 13 | // 14 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used 15 | // to endorse or promote products derived from this software without specific prior written 16 | // permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 19 | // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 20 | // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 21 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 25 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | import CoreFoundation 28 | import Metal 29 | 30 | class PageAlignedArrayImpl { 31 | var space: Int 32 | var ptr: UnsafeMutablePointer 33 | 34 | static private func alignedAlloc(count: Int) -> UnsafeMutablePointer { 35 | var newAddr:UnsafeMutableRawPointer? 36 | let alignment : Int = Int(getpagesize()) 37 | var size : Int 38 | 39 | if count == 0 { 40 | size = MemoryLayout.size / Int(getpagesize()) 41 | 42 | if MemoryLayout.size % Int(getpagesize()) != 0 { 43 | size += Int(getpagesize()) 44 | } 45 | } else { 46 | size = Int(count * MemoryLayout.size) 47 | } 48 | 49 | posix_memalign(&newAddr, alignment, size) 50 | 51 | return newAddr!.assumingMemoryBound(to: T.self) 52 | } 53 | 54 | static private func freeAlignedAlloc(addr : UnsafeMutablePointer) { 55 | free(addr) 56 | } 57 | 58 | 59 | init(count: Int = 0, ptr: UnsafeMutablePointer? = nil) { 60 | self.count = count 61 | self.space = Int(getpagesize()) / MemoryLayout.size 62 | 63 | self.ptr = PageAlignedArrayImpl.alignedAlloc(count: count) 64 | 65 | if ptr != nil { 66 | self.ptr.initialize(from: ptr!, count: count) 67 | } 68 | } 69 | 70 | var count : Int { 71 | didSet { 72 | if space <= count { 73 | let newSpace = count * 2 74 | let newPtr = PageAlignedArrayImpl.alignedAlloc(count: newSpace) 75 | 76 | newPtr.moveInitialize(from: ptr, count: oldValue) 77 | 78 | PageAlignedArrayImpl.freeAlignedAlloc(addr: ptr) 79 | ptr = newPtr 80 | space = newSpace 81 | } 82 | } 83 | } 84 | 85 | func copy() -> PageAlignedArrayImpl { 86 | return PageAlignedArrayImpl(count: count, ptr: ptr) 87 | } 88 | 89 | deinit { 90 | ptr.deinitialize(count: count) 91 | PageAlignedArrayImpl.freeAlignedAlloc(addr: ptr) 92 | } 93 | } 94 | 95 | struct PageAlignedContiguousArray: RangeReplaceableCollection { 96 | private var impl: PageAlignedArrayImpl = PageAlignedArrayImpl(count: 0) 97 | 98 | /// Replaces the specified subrange of elements with the given collection. 99 | /// 100 | /// This method has the effect of removing the specified range of elements 101 | /// from the collection and inserting the new elements at the same location. 102 | /// The number of new elements need not match the number of elements being 103 | /// removed. 104 | /// 105 | /// In this example, three elements in the middle of an array of integers are 106 | /// replaced by the five elements of a `Repeated` instance. 107 | /// 108 | /// var nums = [10, 20, 30, 40, 50] 109 | /// nums.replaceSubrange(1...3, with: repeatElement(1, count: 5)) 110 | /// print(nums) 111 | /// // Prints "[10, 1, 1, 1, 1, 1, 50]" 112 | /// 113 | /// If you pass a zero-length range as the `subrange` parameter, this method 114 | /// inserts the elements of `newElements` at `subrange.startIndex`. Calling 115 | /// the `insert(contentsOf:at:)` method instead is preferred. 116 | /// 117 | /// Likewise, if you pass a zero-length collection as the `newElements` 118 | /// parameter, this method removes the elements in the given subrange 119 | /// without replacement. Calling the `removeSubrange(_:)` method instead is 120 | /// preferred. 121 | /// 122 | /// Calling this method may invalidate any existing indices for use with this 123 | /// collection. 124 | /// 125 | /// - Parameters: 126 | /// - subrange: The subrange of the collection to replace. The bounds of 127 | /// the range must be valid indices of the collection. 128 | /// - newElements: The new elements to add to the collection. 129 | /// 130 | /// - Complexity: O(*m*), where *m* is the combined length of the collection 131 | /// and `newElements`. If the call to `replaceSubrange` simply appends the 132 | /// contents of `newElements` to the collection, the complexity is O(*n*), 133 | /// where *n* is the length of `newElements`. 134 | public mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C : Collection, C.Iterator.Element == T { 135 | let newCount = newElements.count as! Int 136 | let oldCount = self.count 137 | let eraseCount = subrange.count 138 | 139 | let growth = newCount - eraseCount 140 | impl.count = oldCount + growth 141 | 142 | let elements = impl.ptr 143 | let oldTailIndex = subrange.upperBound 144 | let oldTailStart = elements + oldTailIndex 145 | let newTailIndex = oldTailIndex + growth 146 | let newTailStart = oldTailStart + growth 147 | let tailCount = oldCount - subrange.upperBound 148 | 149 | if growth > 0 { 150 | // Slide the tail part of the buffer forwards, in reverse order 151 | // so as not to self-clobber. 152 | newTailStart.moveInitialize(from: oldTailStart, count: tailCount) 153 | 154 | // Assign over the original subRange 155 | var i = newElements.startIndex 156 | for j in CountableRange(subrange) { 157 | elements[j] = newElements[i] 158 | newElements.formIndex(after: &i) 159 | } 160 | // Initialize the hole left by sliding the tail forward 161 | for j in oldTailIndex.. shrinkage { // If the tail length exceeds the shrinkage 184 | 185 | // Assign over the rest of the replaced range with the first 186 | // part of the tail. 187 | newTailStart.moveAssign(from: oldTailStart, count: shrinkage) 188 | 189 | // Slide the rest of the tail back 190 | oldTailStart.moveInitialize( 191 | from: oldTailStart + shrinkage, count: tailCount - shrinkage) 192 | } 193 | else { // Tail fits within erased elements 194 | // Assign over the start of the replaced range with the tail 195 | newTailStart.moveAssign(from: oldTailStart, count: tailCount) 196 | 197 | // Destroy elements remaining after the tail in subRange 198 | (newTailStart + tailCount).deinitialize( 199 | count: shrinkage - tailCount) 200 | } 201 | } 202 | } 203 | 204 | /// Returns the position immediately after the given index. 205 | /// 206 | /// - Parameter i: A valid index of the collection. `i` must be less than 207 | /// `endIndex`. 208 | /// - Returns: The index value immediately after `i`. 209 | public func index(after i: Int) -> Int { 210 | return i + 1 211 | } 212 | 213 | var buffer : UnsafeMutablePointer { 214 | return impl.ptr 215 | } 216 | 217 | var bufferLength : Int { 218 | return impl.space * MemoryLayout.size 219 | } 220 | 221 | 222 | var count: Int { 223 | return impl.count 224 | } 225 | 226 | subscript(index: Int) -> T { 227 | get { 228 | assert (index < count, "Array index out of range") 229 | return impl.ptr[index] 230 | } 231 | mutating set { 232 | assert (index < count, "Array index out of range") 233 | impl.ptr[index] = newValue 234 | } 235 | } 236 | 237 | var description: String { 238 | return String(format: "Aligned buffer: %x", impl.ptr) 239 | } 240 | 241 | typealias Index = Int 242 | 243 | var startIndex: Index { 244 | return 0 245 | } 246 | 247 | var endIndex: Index { 248 | return count 249 | } 250 | 251 | typealias Generator = AnyIterator 252 | 253 | func generate() -> Generator { 254 | var index = 0 255 | return AnyIterator { 256 | if index < self.count { 257 | index += 1 258 | return self[index] 259 | } else { 260 | return nil 261 | } 262 | } 263 | } 264 | } 265 | 266 | extension PageAlignedContiguousArray : ExpressibleByArrayLiteral { 267 | public init(arrayLiteral elements: T...) { 268 | self.init() 269 | for element in elements { 270 | append(element) 271 | } 272 | } 273 | } 274 | 275 | extension MTLDevice { 276 | func makeBufferWithPageAlignedArray(_ array: PageAlignedContiguousArray) -> MTLBuffer? { 277 | return self.makeBuffer(bytesNoCopy: array.buffer, length: array.bufferLength, options: .storageModeShared, deallocator: nil) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /PageAlignedArray.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 51160A8E1DAEF9EE0068C668 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51160A8D1DAEF9EE0068C668 /* AppDelegate.swift */; }; 11 | 51160A901DAEF9EE0068C668 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51160A8F1DAEF9EE0068C668 /* ViewController.swift */; }; 12 | 51160A931DAEF9EE0068C668 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51160A911DAEF9EE0068C668 /* Main.storyboard */; }; 13 | 51160A951DAEF9EE0068C668 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51160A941DAEF9EE0068C668 /* Assets.xcassets */; }; 14 | 51160A981DAEF9EE0068C668 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51160A961DAEF9EE0068C668 /* LaunchScreen.storyboard */; }; 15 | 51160AA01DAEFA030068C668 /* PageAlignedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51160A9F1DAEFA030068C668 /* PageAlignedArray.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 51160A8A1DAEF9EE0068C668 /* PageAlignedArray.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PageAlignedArray.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 51160A8D1DAEF9EE0068C668 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 51160A8F1DAEF9EE0068C668 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 51160A921DAEF9EE0068C668 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 51160A941DAEF9EE0068C668 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 51160A971DAEF9EE0068C668 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 25 | 51160A991DAEF9EE0068C668 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 51160A9F1DAEFA030068C668 /* PageAlignedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageAlignedArray.swift; sourceTree = SOURCE_ROOT; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 51160A871DAEF9EE0068C668 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 51160A811DAEF9ED0068C668 = { 41 | isa = PBXGroup; 42 | children = ( 43 | 51160A8C1DAEF9EE0068C668 /* PageAlignedArray */, 44 | 51160A8B1DAEF9EE0068C668 /* Products */, 45 | ); 46 | sourceTree = ""; 47 | }; 48 | 51160A8B1DAEF9EE0068C668 /* Products */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 51160A8A1DAEF9EE0068C668 /* PageAlignedArray.app */, 52 | ); 53 | name = Products; 54 | sourceTree = ""; 55 | }; 56 | 51160A8C1DAEF9EE0068C668 /* PageAlignedArray */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 51160A9F1DAEFA030068C668 /* PageAlignedArray.swift */, 60 | 51160A8D1DAEF9EE0068C668 /* AppDelegate.swift */, 61 | 51160A8F1DAEF9EE0068C668 /* ViewController.swift */, 62 | 51160A911DAEF9EE0068C668 /* Main.storyboard */, 63 | 51160A941DAEF9EE0068C668 /* Assets.xcassets */, 64 | 51160A961DAEF9EE0068C668 /* LaunchScreen.storyboard */, 65 | 51160A991DAEF9EE0068C668 /* Info.plist */, 66 | ); 67 | path = PageAlignedArray; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | 51160A891DAEF9EE0068C668 /* PageAlignedArray */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = 51160A9C1DAEF9EE0068C668 /* Build configuration list for PBXNativeTarget "PageAlignedArray" */; 76 | buildPhases = ( 77 | 51160A861DAEF9EE0068C668 /* Sources */, 78 | 51160A871DAEF9EE0068C668 /* Frameworks */, 79 | 51160A881DAEF9EE0068C668 /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = PageAlignedArray; 86 | productName = PageAlignedArray; 87 | productReference = 51160A8A1DAEF9EE0068C668 /* PageAlignedArray.app */; 88 | productType = "com.apple.product-type.application"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | 51160A821DAEF9ED0068C668 /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastSwiftUpdateCheck = 0800; 97 | LastUpgradeCheck = 0800; 98 | ORGANIZATIONNAME = "Eldad Eilam"; 99 | TargetAttributes = { 100 | 51160A891DAEF9EE0068C668 = { 101 | CreatedOnToolsVersion = 8.0; 102 | DevelopmentTeam = E54FNFV6AU; 103 | ProvisioningStyle = Automatic; 104 | }; 105 | }; 106 | }; 107 | buildConfigurationList = 51160A851DAEF9ED0068C668 /* Build configuration list for PBXProject "PageAlignedArray" */; 108 | compatibilityVersion = "Xcode 3.2"; 109 | developmentRegion = English; 110 | hasScannedForEncodings = 0; 111 | knownRegions = ( 112 | en, 113 | Base, 114 | ); 115 | mainGroup = 51160A811DAEF9ED0068C668; 116 | productRefGroup = 51160A8B1DAEF9EE0068C668 /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | 51160A891DAEF9EE0068C668 /* PageAlignedArray */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXResourcesBuildPhase section */ 126 | 51160A881DAEF9EE0068C668 /* Resources */ = { 127 | isa = PBXResourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 51160A981DAEF9EE0068C668 /* LaunchScreen.storyboard in Resources */, 131 | 51160A951DAEF9EE0068C668 /* Assets.xcassets in Resources */, 132 | 51160A931DAEF9EE0068C668 /* Main.storyboard in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | 51160A861DAEF9EE0068C668 /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 51160A901DAEF9EE0068C668 /* ViewController.swift in Sources */, 144 | 51160AA01DAEFA030068C668 /* PageAlignedArray.swift in Sources */, 145 | 51160A8E1DAEF9EE0068C668 /* AppDelegate.swift in Sources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXSourcesBuildPhase section */ 150 | 151 | /* Begin PBXVariantGroup section */ 152 | 51160A911DAEF9EE0068C668 /* Main.storyboard */ = { 153 | isa = PBXVariantGroup; 154 | children = ( 155 | 51160A921DAEF9EE0068C668 /* Base */, 156 | ); 157 | name = Main.storyboard; 158 | sourceTree = ""; 159 | }; 160 | 51160A961DAEF9EE0068C668 /* LaunchScreen.storyboard */ = { 161 | isa = PBXVariantGroup; 162 | children = ( 163 | 51160A971DAEF9EE0068C668 /* Base */, 164 | ); 165 | name = LaunchScreen.storyboard; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXVariantGroup section */ 169 | 170 | /* Begin XCBuildConfiguration section */ 171 | 51160A9A1DAEF9EE0068C668 /* Debug */ = { 172 | isa = XCBuildConfiguration; 173 | buildSettings = { 174 | ALWAYS_SEARCH_USER_PATHS = NO; 175 | CLANG_ANALYZER_NONNULL = YES; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_CONSTANT_CONVERSION = YES; 182 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 183 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 184 | CLANG_WARN_EMPTY_BODY = YES; 185 | CLANG_WARN_ENUM_CONVERSION = YES; 186 | CLANG_WARN_INFINITE_RECURSION = YES; 187 | CLANG_WARN_INT_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 193 | COPY_PHASE_STRIP = NO; 194 | DEBUG_INFORMATION_FORMAT = dwarf; 195 | ENABLE_STRICT_OBJC_MSGSEND = YES; 196 | ENABLE_TESTABILITY = YES; 197 | GCC_C_LANGUAGE_STANDARD = gnu99; 198 | GCC_DYNAMIC_NO_PIC = NO; 199 | GCC_NO_COMMON_BLOCKS = YES; 200 | GCC_OPTIMIZATION_LEVEL = 0; 201 | GCC_PREPROCESSOR_DEFINITIONS = ( 202 | "DEBUG=1", 203 | "$(inherited)", 204 | ); 205 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 206 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 207 | GCC_WARN_UNDECLARED_SELECTOR = YES; 208 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 209 | GCC_WARN_UNUSED_FUNCTION = YES; 210 | GCC_WARN_UNUSED_VARIABLE = YES; 211 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 212 | MTL_ENABLE_DEBUG_INFO = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = iphoneos; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | TARGETED_DEVICE_FAMILY = "1,2"; 218 | }; 219 | name = Debug; 220 | }; 221 | 51160A9B1DAEF9EE0068C668 /* Release */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | CLANG_ANALYZER_NONNULL = YES; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INFINITE_RECURSION = YES; 237 | CLANG_WARN_INT_CONVERSION = YES; 238 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 239 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 240 | CLANG_WARN_UNREACHABLE_CODE = YES; 241 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 242 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 243 | COPY_PHASE_STRIP = NO; 244 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 245 | ENABLE_NS_ASSERTIONS = NO; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | GCC_C_LANGUAGE_STANDARD = gnu99; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 256 | MTL_ENABLE_DEBUG_INFO = NO; 257 | SDKROOT = iphoneos; 258 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 259 | TARGETED_DEVICE_FAMILY = "1,2"; 260 | VALIDATE_PRODUCT = YES; 261 | }; 262 | name = Release; 263 | }; 264 | 51160A9D1DAEF9EE0068C668 /* Debug */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 268 | DEVELOPMENT_TEAM = E54FNFV6AU; 269 | INFOPLIST_FILE = PageAlignedArray/Info.plist; 270 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 271 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 272 | PRODUCT_BUNDLE_IDENTIFIER = com.eldade.PageAlignedArray; 273 | PRODUCT_NAME = "$(TARGET_NAME)"; 274 | SWIFT_VERSION = 3.0; 275 | }; 276 | name = Debug; 277 | }; 278 | 51160A9E1DAEF9EE0068C668 /* Release */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 282 | DEVELOPMENT_TEAM = E54FNFV6AU; 283 | INFOPLIST_FILE = PageAlignedArray/Info.plist; 284 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 285 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 286 | PRODUCT_BUNDLE_IDENTIFIER = com.eldade.PageAlignedArray; 287 | PRODUCT_NAME = "$(TARGET_NAME)"; 288 | SWIFT_VERSION = 3.0; 289 | }; 290 | name = Release; 291 | }; 292 | /* End XCBuildConfiguration section */ 293 | 294 | /* Begin XCConfigurationList section */ 295 | 51160A851DAEF9ED0068C668 /* Build configuration list for PBXProject "PageAlignedArray" */ = { 296 | isa = XCConfigurationList; 297 | buildConfigurations = ( 298 | 51160A9A1DAEF9EE0068C668 /* Debug */, 299 | 51160A9B1DAEF9EE0068C668 /* Release */, 300 | ); 301 | defaultConfigurationIsVisible = 0; 302 | defaultConfigurationName = Release; 303 | }; 304 | 51160A9C1DAEF9EE0068C668 /* Build configuration list for PBXNativeTarget "PageAlignedArray" */ = { 305 | isa = XCConfigurationList; 306 | buildConfigurations = ( 307 | 51160A9D1DAEF9EE0068C668 /* Debug */, 308 | 51160A9E1DAEF9EE0068C668 /* Release */, 309 | ); 310 | defaultConfigurationIsVisible = 0; 311 | }; 312 | /* End XCConfigurationList section */ 313 | }; 314 | rootObject = 51160A821DAEF9ED0068C668 /* Project object */; 315 | } 316 | -------------------------------------------------------------------------------- /PageAlignedArray.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PageAlignedArray.xcodeproj/xcuserdata/eldade.xcuserdatad/xcschemes/PageAlignedArray.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /PageAlignedArray.xcodeproj/xcuserdata/eldade.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PageAlignedArray.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 51160A891DAEF9EE0068C668 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PageAlignedArray/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Eldad Eilam 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, are 5 | // permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this list of 8 | // conditions and the following disclaimer. 9 | // 10 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | // of conditions and the following disclaimer in the documentation and/or other materials 12 | // provided with the distribution. 13 | // 14 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used 15 | // to endorse or promote products derived from this software without specific prior written 16 | // permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 19 | // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 20 | // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 21 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 25 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | import UIKit 28 | 29 | @UIApplicationMain 30 | class AppDelegate: UIResponder, UIApplicationDelegate { 31 | 32 | var window: UIWindow? 33 | 34 | 35 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 36 | // Override point for customization after application launch. 37 | return true 38 | } 39 | 40 | func applicationWillResignActive(_ application: UIApplication) { 41 | // 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. 42 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 43 | } 44 | 45 | func applicationDidEnterBackground(_ application: UIApplication) { 46 | // 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. 47 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 48 | } 49 | 50 | func applicationWillEnterForeground(_ application: UIApplication) { 51 | // 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. 52 | } 53 | 54 | func applicationDidBecomeActive(_ application: UIApplication) { 55 | // 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. 56 | } 57 | 58 | func applicationWillTerminate(_ application: UIApplication) { 59 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /PageAlignedArray/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 | } -------------------------------------------------------------------------------- /PageAlignedArray/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 | -------------------------------------------------------------------------------- /PageAlignedArray/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 | -------------------------------------------------------------------------------- /PageAlignedArray/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /PageAlignedArray/ViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Eldad Eilam 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, are 5 | // permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this list of 8 | // conditions and the following disclaimer. 9 | // 10 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | // of conditions and the following disclaimer in the documentation and/or other materials 12 | // provided with the distribution. 13 | // 14 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used 15 | // to endorse or promote products derived from this software without specific prior written 16 | // permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 19 | // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 20 | // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 21 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 25 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | import UIKit 28 | import Metal 29 | import MetalKit 30 | 31 | extension vector_double4 { 32 | static func == (lhs: vector_double4, rhs: vector_double4) -> Bool { 33 | if lhs.w == rhs.w && lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z { 34 | return true 35 | } else { 36 | return false 37 | } 38 | } 39 | } 40 | 41 | extension matrix_double4x4 { 42 | static func == (lhs: matrix_double4x4, rhs: matrix_double4x4) -> Bool { 43 | if lhs.columns.0 == rhs.columns.0 && 44 | lhs.columns.1 == rhs.columns.1 && 45 | lhs.columns.2 == rhs.columns.2 && 46 | lhs.columns.3 == rhs.columns.3 { 47 | return true 48 | } else { 49 | return false 50 | } 51 | } 52 | 53 | static func != (lhs: matrix_double4x4, rhs: matrix_double4x4) -> Bool { 54 | return lhs == rhs ? false : true 55 | } 56 | 57 | } 58 | 59 | class ViewController: UIViewController { 60 | 61 | override func viewDidLoad() { 62 | super.viewDidLoad() 63 | 64 | let testVector = vector_double4(0, 1, 2, 3) 65 | var matrixTest = matrix_double4x4.init(columns: (testVector, testVector, testVector, testVector)) 66 | 67 | var alignedArrayEmpty : PageAlignedContiguousArray 68 | var alignedArrayInitializer = PageAlignedContiguousArray(repeating: matrixTest, count: 4096) 69 | var alignedArrayLiteral : PageAlignedContiguousArray = [matrixTest, matrixTest] 70 | 71 | var current = 0.0 72 | for (index, _) in alignedArrayInitializer.enumerated() { 73 | alignedArrayInitializer[index] = matrix_double4x4.init(columns: (vector_double4(current), vector_double4(current), vector_double4(current), vector_double4(current))) 74 | current += 1.0 75 | } 76 | 77 | current = 0.0 78 | repeat { 79 | matrixTest.columns = (vector_double4(current), vector_double4(current), vector_double4(current), vector_double4(current)) 80 | 81 | let currentMat = alignedArrayInitializer.first 82 | if currentMat! != matrixTest { 83 | print ("error") 84 | } 85 | current += 1.0 86 | alignedArrayInitializer.removeFirst() 87 | } while (alignedArrayInitializer.count != 0) 88 | 89 | let device = MTLCreateSystemDefaultDevice() 90 | let testMetalBuffer = device?.makeBufferWithPageAlignedArray(alignedArrayInitializer) 91 | 92 | } 93 | 94 | override func didReceiveMemoryWarning() { 95 | super.didReceiveMemoryWarning() 96 | // Dispose of any resources that can be recreated. 97 | } 98 | 99 | 100 | } 101 | 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PageAlignedArray 2 | 3 | `PageAlignedArray` is a Swift class that implements a version of the built-in Swift array, but with guaranteed page-aligned storage buffers. 4 | This is useful for iOS/macOS applications that use the Metal API. If you have large buffers with vertexes or any other data that changes between frames, it is highly recommended that you load that data to the GPU using shared memory (between the CPU and the GPU). Unfortunately, allocating page-aligned memory and managing its contents in Swift can be a pain. 5 | 6 | For this reason I've created `PageAlignedArray`. It makes it a breeze to create and manipulate an array containing any data type, and then turn into a shared-memory Metal buffer. Here's a quick code sample: 7 | ``` 8 | var alignedArray : PageAlignedContiguousArray = [matrixTest, matrixTest] 9 | alignedArray.append(item) 10 | alignedArray.removeFirst() // Behaves just like a built-in array, with all convenience methods 11 | 12 | // When it's time to generate a Metal buffer: 13 | let device = MTLCreateSystemDefaultDevice() 14 | let testMetalBuffer = device?.makeBufferWithPageAlignedArray(alignedArray) 15 | 16 | // Of course, because we're in shared memory, we can continue to manipulate the contents of the 17 | // array after generating the buffer and the GPU sees the latest data. 18 | ``` 19 | 20 | **NOTE:** While it is safe to mutate the contents of the array after generating a buffer, keep in mind that you must generate a new buffer each time the length of the array changes, otherwise Metal won't know the array's new length. 21 | 22 | ## Suitable for large datasets 23 | Because of the page-alignment constraint, `PageAlignedArray` has a minimum allocation size of one page, which translates to 16Kb on most iOS devices. This means that it is mostly suitable for larger, 100Kb+ datasets. Smaller datasets will cause significant memory waste with negligible performance benefits. 24 | --------------------------------------------------------------------------------