├── .DS_Store ├── LICENSE ├── MetalScroller.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── sjuyal.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── sjuyal.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── MetalScroller ├── .DS_Store ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── .DS_Store │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── ColorMap.textureset │ │ ├── Contents.json │ │ └── Universal.mipmapset │ │ │ ├── ColorMap.png │ │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── CubeView.h ├── CubeView.m ├── GameViewController.h ├── GameViewController.m ├── Info.plist ├── Renderer.h ├── Renderer.m ├── ShaderTypes.h ├── Shaders.metal └── main.m └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunkyguy/MetalScroller/8a8980d8ade1230886d5d156358848714aa58159/.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sidharth Juyal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MetalScroller.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 792C3DD0264CFD5700E4BA36 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 792C3DCF264CFD5700E4BA36 /* AppDelegate.m */; }; 11 | 792C3DD3264CFD5700E4BA36 /* Renderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 792C3DD2264CFD5700E4BA36 /* Renderer.m */; }; 12 | 792C3DD6264CFD5700E4BA36 /* GameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 792C3DD5264CFD5700E4BA36 /* GameViewController.m */; }; 13 | 792C3DD8264CFD5700E4BA36 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 792C3DD7264CFD5700E4BA36 /* Shaders.metal */; }; 14 | 792C3DDC264CFD5700E4BA36 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 792C3DDA264CFD5700E4BA36 /* Main.storyboard */; }; 15 | 792C3DDE264CFD5800E4BA36 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 792C3DDD264CFD5800E4BA36 /* Assets.xcassets */; }; 16 | 792C3DE1264CFD5800E4BA36 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 792C3DE0264CFD5800E4BA36 /* main.m */; }; 17 | 792C3DE9264CFDFA00E4BA36 /* CubeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 792C3DE8264CFDFA00E4BA36 /* CubeView.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 792C3DCB264CFD5700E4BA36 /* MetalScroller.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MetalScroller.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 792C3DCE264CFD5700E4BA36 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 23 | 792C3DCF264CFD5700E4BA36 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 24 | 792C3DD1264CFD5700E4BA36 /* Renderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Renderer.h; sourceTree = ""; }; 25 | 792C3DD2264CFD5700E4BA36 /* Renderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Renderer.m; sourceTree = ""; }; 26 | 792C3DD4264CFD5700E4BA36 /* GameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameViewController.h; sourceTree = ""; }; 27 | 792C3DD5264CFD5700E4BA36 /* GameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameViewController.m; sourceTree = ""; }; 28 | 792C3DD7264CFD5700E4BA36 /* Shaders.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = ""; }; 29 | 792C3DD9264CFD5700E4BA36 /* ShaderTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShaderTypes.h; sourceTree = ""; }; 30 | 792C3DDB264CFD5700E4BA36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | 792C3DDD264CFD5800E4BA36 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | 792C3DDF264CFD5800E4BA36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 792C3DE0264CFD5800E4BA36 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 34 | 792C3DE7264CFDFA00E4BA36 /* CubeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CubeView.h; sourceTree = ""; }; 35 | 792C3DE8264CFDFA00E4BA36 /* CubeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CubeView.m; sourceTree = ""; }; 36 | 79C73B2F264D12CA0034A622 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 792C3DC8264CFD5700E4BA36 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 792C3DC2264CFD5700E4BA36 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 79C73B2F264D12CA0034A622 /* README.md */, 54 | 792C3DCD264CFD5700E4BA36 /* MetalScroller */, 55 | 792C3DCC264CFD5700E4BA36 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | 792C3DCC264CFD5700E4BA36 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 792C3DCB264CFD5700E4BA36 /* MetalScroller.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | 792C3DCD264CFD5700E4BA36 /* MetalScroller */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 792C3DCE264CFD5700E4BA36 /* AppDelegate.h */, 71 | 792C3DCF264CFD5700E4BA36 /* AppDelegate.m */, 72 | 792C3DD1264CFD5700E4BA36 /* Renderer.h */, 73 | 792C3DD2264CFD5700E4BA36 /* Renderer.m */, 74 | 792C3DD4264CFD5700E4BA36 /* GameViewController.h */, 75 | 792C3DD5264CFD5700E4BA36 /* GameViewController.m */, 76 | 792C3DE7264CFDFA00E4BA36 /* CubeView.h */, 77 | 792C3DE8264CFDFA00E4BA36 /* CubeView.m */, 78 | 792C3DD7264CFD5700E4BA36 /* Shaders.metal */, 79 | 792C3DD9264CFD5700E4BA36 /* ShaderTypes.h */, 80 | 792C3DDA264CFD5700E4BA36 /* Main.storyboard */, 81 | 792C3DDD264CFD5800E4BA36 /* Assets.xcassets */, 82 | 792C3DDF264CFD5800E4BA36 /* Info.plist */, 83 | 792C3DE0264CFD5800E4BA36 /* main.m */, 84 | ); 85 | path = MetalScroller; 86 | sourceTree = ""; 87 | }; 88 | /* End PBXGroup section */ 89 | 90 | /* Begin PBXNativeTarget section */ 91 | 792C3DCA264CFD5700E4BA36 /* MetalScroller */ = { 92 | isa = PBXNativeTarget; 93 | buildConfigurationList = 792C3DE4264CFD5800E4BA36 /* Build configuration list for PBXNativeTarget "MetalScroller" */; 94 | buildPhases = ( 95 | 792C3DC7264CFD5700E4BA36 /* Sources */, 96 | 792C3DC8264CFD5700E4BA36 /* Frameworks */, 97 | 792C3DC9264CFD5700E4BA36 /* Resources */, 98 | ); 99 | buildRules = ( 100 | ); 101 | dependencies = ( 102 | ); 103 | name = MetalScroller; 104 | productName = MetalScroller; 105 | productReference = 792C3DCB264CFD5700E4BA36 /* MetalScroller.app */; 106 | productType = "com.apple.product-type.application"; 107 | }; 108 | /* End PBXNativeTarget section */ 109 | 110 | /* Begin PBXProject section */ 111 | 792C3DC3264CFD5700E4BA36 /* Project object */ = { 112 | isa = PBXProject; 113 | attributes = { 114 | LastUpgradeCheck = 1250; 115 | TargetAttributes = { 116 | 792C3DCA264CFD5700E4BA36 = { 117 | CreatedOnToolsVersion = 12.5; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = 792C3DC6264CFD5700E4BA36 /* Build configuration list for PBXProject "MetalScroller" */; 122 | compatibilityVersion = "Xcode 9.3"; 123 | developmentRegion = en; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = 792C3DC2264CFD5700E4BA36; 130 | productRefGroup = 792C3DCC264CFD5700E4BA36 /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 792C3DCA264CFD5700E4BA36 /* MetalScroller */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 792C3DC9264CFD5700E4BA36 /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 792C3DDE264CFD5800E4BA36 /* Assets.xcassets in Resources */, 145 | 792C3DDC264CFD5700E4BA36 /* Main.storyboard in Resources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXResourcesBuildPhase section */ 150 | 151 | /* Begin PBXSourcesBuildPhase section */ 152 | 792C3DC7264CFD5700E4BA36 /* Sources */ = { 153 | isa = PBXSourcesBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | 792C3DE1264CFD5800E4BA36 /* main.m in Sources */, 157 | 792C3DD8264CFD5700E4BA36 /* Shaders.metal in Sources */, 158 | 792C3DD3264CFD5700E4BA36 /* Renderer.m in Sources */, 159 | 792C3DE9264CFDFA00E4BA36 /* CubeView.m in Sources */, 160 | 792C3DD6264CFD5700E4BA36 /* GameViewController.m in Sources */, 161 | 792C3DD0264CFD5700E4BA36 /* AppDelegate.m in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin PBXVariantGroup section */ 168 | 792C3DDA264CFD5700E4BA36 /* Main.storyboard */ = { 169 | isa = PBXVariantGroup; 170 | children = ( 171 | 792C3DDB264CFD5700E4BA36 /* Base */, 172 | ); 173 | name = Main.storyboard; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXVariantGroup section */ 177 | 178 | /* Begin XCBuildConfiguration section */ 179 | 792C3DE2264CFD5800E4BA36 /* Debug */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | ALWAYS_SEARCH_USER_PATHS = NO; 183 | CLANG_ANALYZER_NONNULL = YES; 184 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 185 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 186 | CLANG_CXX_LIBRARY = "libc++"; 187 | CLANG_ENABLE_MODULES = YES; 188 | CLANG_ENABLE_OBJC_ARC = YES; 189 | CLANG_ENABLE_OBJC_WEAK = YES; 190 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 191 | CLANG_WARN_BOOL_CONVERSION = YES; 192 | CLANG_WARN_COMMA = YES; 193 | CLANG_WARN_CONSTANT_CONVERSION = YES; 194 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 195 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 196 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 197 | CLANG_WARN_EMPTY_BODY = YES; 198 | CLANG_WARN_ENUM_CONVERSION = YES; 199 | CLANG_WARN_INFINITE_RECURSION = YES; 200 | CLANG_WARN_INT_CONVERSION = YES; 201 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 202 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 203 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 204 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 205 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 206 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 207 | CLANG_WARN_STRICT_PROTOTYPES = YES; 208 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 209 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 210 | CLANG_WARN_UNREACHABLE_CODE = YES; 211 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 212 | COPY_PHASE_STRIP = NO; 213 | DEBUG_INFORMATION_FORMAT = dwarf; 214 | ENABLE_STRICT_OBJC_MSGSEND = YES; 215 | ENABLE_TESTABILITY = YES; 216 | GCC_C_LANGUAGE_STANDARD = gnu11; 217 | GCC_DYNAMIC_NO_PIC = NO; 218 | GCC_NO_COMMON_BLOCKS = YES; 219 | GCC_OPTIMIZATION_LEVEL = 0; 220 | GCC_PREPROCESSOR_DEFINITIONS = ( 221 | "DEBUG=1", 222 | "$(inherited)", 223 | ); 224 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 225 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 226 | GCC_WARN_UNDECLARED_SELECTOR = YES; 227 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 228 | GCC_WARN_UNUSED_FUNCTION = YES; 229 | GCC_WARN_UNUSED_VARIABLE = YES; 230 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 231 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 232 | MTL_FAST_MATH = YES; 233 | ONLY_ACTIVE_ARCH = YES; 234 | SDKROOT = iphoneos; 235 | }; 236 | name = Debug; 237 | }; 238 | 792C3DE3264CFD5800E4BA36 /* Release */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | CLANG_ANALYZER_NONNULL = YES; 243 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 244 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 245 | CLANG_CXX_LIBRARY = "libc++"; 246 | CLANG_ENABLE_MODULES = YES; 247 | CLANG_ENABLE_OBJC_ARC = YES; 248 | CLANG_ENABLE_OBJC_WEAK = YES; 249 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 250 | CLANG_WARN_BOOL_CONVERSION = YES; 251 | CLANG_WARN_COMMA = YES; 252 | CLANG_WARN_CONSTANT_CONVERSION = YES; 253 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 255 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 266 | CLANG_WARN_STRICT_PROTOTYPES = YES; 267 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 268 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | COPY_PHASE_STRIP = NO; 272 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 273 | ENABLE_NS_ASSERTIONS = NO; 274 | ENABLE_STRICT_OBJC_MSGSEND = YES; 275 | GCC_C_LANGUAGE_STANDARD = gnu11; 276 | GCC_NO_COMMON_BLOCKS = YES; 277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 279 | GCC_WARN_UNDECLARED_SELECTOR = YES; 280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 281 | GCC_WARN_UNUSED_FUNCTION = YES; 282 | GCC_WARN_UNUSED_VARIABLE = YES; 283 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 284 | MTL_ENABLE_DEBUG_INFO = NO; 285 | MTL_FAST_MATH = YES; 286 | SDKROOT = iphoneos; 287 | VALIDATE_PRODUCT = YES; 288 | }; 289 | name = Release; 290 | }; 291 | 792C3DE5264CFD5800E4BA36 /* Debug */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 295 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 296 | CODE_SIGN_STYLE = Automatic; 297 | INFOPLIST_FILE = MetalScroller/Info.plist; 298 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 299 | LD_RUNPATH_SEARCH_PATHS = ( 300 | "$(inherited)", 301 | "@executable_path/Frameworks", 302 | ); 303 | PRODUCT_BUNDLE_IDENTIFIER = com.wl.MetalScroller; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | }; 307 | name = Debug; 308 | }; 309 | 792C3DE6264CFD5800E4BA36 /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 313 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 314 | CODE_SIGN_STYLE = Automatic; 315 | INFOPLIST_FILE = MetalScroller/Info.plist; 316 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 317 | LD_RUNPATH_SEARCH_PATHS = ( 318 | "$(inherited)", 319 | "@executable_path/Frameworks", 320 | ); 321 | PRODUCT_BUNDLE_IDENTIFIER = com.wl.MetalScroller; 322 | PRODUCT_NAME = "$(TARGET_NAME)"; 323 | TARGETED_DEVICE_FAMILY = "1,2"; 324 | }; 325 | name = Release; 326 | }; 327 | /* End XCBuildConfiguration section */ 328 | 329 | /* Begin XCConfigurationList section */ 330 | 792C3DC6264CFD5700E4BA36 /* Build configuration list for PBXProject "MetalScroller" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | 792C3DE2264CFD5800E4BA36 /* Debug */, 334 | 792C3DE3264CFD5800E4BA36 /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Release; 338 | }; 339 | 792C3DE4264CFD5800E4BA36 /* Build configuration list for PBXNativeTarget "MetalScroller" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | 792C3DE5264CFD5800E4BA36 /* Debug */, 343 | 792C3DE6264CFD5800E4BA36 /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | /* End XCConfigurationList section */ 349 | }; 350 | rootObject = 792C3DC3264CFD5700E4BA36 /* Project object */; 351 | } 352 | -------------------------------------------------------------------------------- /MetalScroller.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetalScroller.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MetalScroller.xcodeproj/project.xcworkspace/xcuserdata/sjuyal.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunkyguy/MetalScroller/8a8980d8ade1230886d5d156358848714aa58159/MetalScroller.xcodeproj/project.xcworkspace/xcuserdata/sjuyal.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MetalScroller.xcodeproj/xcuserdata/sjuyal.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MetalScroller.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MetalScroller/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunkyguy/MetalScroller/8a8980d8ade1230886d5d156358848714aa58159/MetalScroller/.DS_Store -------------------------------------------------------------------------------- /MetalScroller/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import 7 | 8 | @interface AppDelegate : UIResponder 9 | 10 | @property (strong, nonatomic) UIWindow *window; 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /MetalScroller/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import "AppDelegate.h" 7 | 8 | @interface AppDelegate () 9 | 10 | @end 11 | 12 | @implementation AppDelegate 13 | 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 16 | // Override point for customization after application launch. 17 | return YES; 18 | } 19 | 20 | 21 | - (void)applicationWillResignActive:(UIApplication *)application { 22 | // 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. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | 27 | - (void)applicationDidEnterBackground:(UIApplication *)application { 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 | } 30 | 31 | 32 | - (void)applicationWillEnterForeground:(UIApplication *)application { 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 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /MetalScroller/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunkyguy/MetalScroller/8a8980d8ade1230886d5d156358848714aa58159/MetalScroller/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /MetalScroller/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MetalScroller/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /MetalScroller/Assets.xcassets/ColorMap.textureset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "properties" : { 7 | "origin" : "bottom-left", 8 | "interpretation" : "non-premultiplied-colors" 9 | }, 10 | "textures" : [ 11 | { 12 | "idiom" : "universal", 13 | "filename" : "Universal.mipmapset" 14 | } 15 | ] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /MetalScroller/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/ColorMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunkyguy/MetalScroller/8a8980d8ade1230886d5d156358848714aa58159/MetalScroller/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/ColorMap.png -------------------------------------------------------------------------------- /MetalScroller/Assets.xcassets/ColorMap.textureset/Universal.mipmapset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "levels" : [ 7 | { 8 | "filename" : "ColorMap.png", 9 | "mipmap-level" : "base" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /MetalScroller/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MetalScroller/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /MetalScroller/CubeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import 7 | 8 | NS_ASSUME_NONNULL_BEGIN 9 | 10 | @interface CubeView : MTKView 11 | @property (nonatomic) CGPoint scrollOffset; 12 | @end 13 | 14 | NS_ASSUME_NONNULL_END 15 | -------------------------------------------------------------------------------- /MetalScroller/CubeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import "CubeView.h" 7 | #import 8 | #import "Renderer.h" 9 | 10 | @implementation CubeView 11 | { 12 | Renderer *_renderer; 13 | } 14 | 15 | - (instancetype)initWithCoder:(NSCoder *)coder 16 | { 17 | self = [super initWithCoder:coder]; 18 | if (self) { 19 | [self setup]; 20 | } 21 | return self; 22 | } 23 | 24 | - (instancetype)initWithFrame:(CGRect)frame 25 | { 26 | self = [super initWithFrame:frame]; 27 | if (self) { 28 | [self setup]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)setup 34 | { 35 | self.device = MTLCreateSystemDefaultDevice(); 36 | self.backgroundColor = UIColor.whiteColor; 37 | 38 | _renderer = [[Renderer alloc] initWithMetalKitView:self]; 39 | [_renderer mtkView:self drawableSizeWillChange:self.bounds.size]; 40 | self.delegate = _renderer; 41 | 42 | [self _randomizeColor]; 43 | } 44 | 45 | - (void)setScrollOffset:(CGPoint)scrollOffset 46 | { 47 | _renderer.contentOffset = scrollOffset.x; 48 | } 49 | 50 | - (void)_randomizeColor 51 | { 52 | CGFloat hue = arc4random() / (CGFloat)UINT32_MAX; 53 | UIColor *clearColor = [UIColor colorWithHue:hue saturation:0.7 brightness:0.8 alpha:1]; 54 | CGFloat r, g, b, a; 55 | [clearColor getRed:&r green:&g blue:&b alpha:&a]; 56 | _renderer.clearColor = MTLClearColorMake(r, g, b, a); 57 | } 58 | 59 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 60 | { 61 | [self _randomizeColor]; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /MetalScroller/GameViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import 7 | #import 8 | #import 9 | #import "Renderer.h" 10 | 11 | // Our iOS view controller 12 | @interface GameViewController : UIViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /MetalScroller/GameViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import "GameViewController.h" 7 | #import "CubeView.h" 8 | 9 | @interface GameViewController () 10 | 11 | @end 12 | 13 | @implementation GameViewController 14 | { 15 | CubeView *_cubeView; 16 | } 17 | 18 | - (void)viewDidLoad 19 | { 20 | [super viewDidLoad]; 21 | 22 | _cubeView = (CubeView *)self.view; 23 | 24 | CGRect cubeVwFrame = [_cubeView bounds]; 25 | UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:cubeVwFrame]; 26 | scrollView.contentSize = CGSizeMake(CGRectGetWidth(cubeVwFrame) * 2, CGRectGetHeight(cubeVwFrame)); 27 | scrollView.showsHorizontalScrollIndicator = NO; 28 | scrollView.delegate = self; 29 | scrollView.hidden = YES; 30 | [self.view addSubview:scrollView]; 31 | 32 | UIView *dummyView = [[UIView alloc] initWithFrame:scrollView.frame]; 33 | [dummyView addGestureRecognizer:scrollView.panGestureRecognizer]; 34 | [self.view addSubview:dummyView]; 35 | } 36 | 37 | #pragma mark UIScrollViewDelegate 38 | 39 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 40 | { 41 | _cubeView.scrollOffset = scrollView.contentOffset; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /MetalScroller/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSupportsIndirectInputEvents 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | metal 31 | 32 | UIStatusBarHidden 33 | 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /MetalScroller/Renderer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import 7 | 8 | // Our platform independent renderer class. Implements the MTKViewDelegate protocol which 9 | // allows it to accept per-frame update and drawable resize callbacks. 10 | @interface Renderer : NSObject 11 | 12 | -(nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)view; 13 | @property (nonatomic) float contentOffset; 14 | @property (nonatomic) MTLClearColor clearColor; 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /MetalScroller/Renderer.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import 7 | #import 8 | #import "Renderer.h" 9 | #import "ShaderTypes.h" 10 | 11 | #define kCubeCount 3 12 | #define kMinX -2.0 13 | #define kMaxX 10.0 14 | 15 | @implementation Renderer 16 | { 17 | id _device; 18 | id _commandQueue; 19 | 20 | id _dynamicUniformBuffer; 21 | id _pipelineState; 22 | id _depthState; 23 | id _colorMap; 24 | MTLVertexDescriptor *_mtlVertexDescriptor; 25 | 26 | matrix_float4x4 _projectionMatrix; 27 | 28 | float _rotation[kCubeCount]; 29 | 30 | MTKMesh *_mesh; 31 | } 32 | 33 | -(nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)view; 34 | { 35 | self = [super init]; 36 | if(self) 37 | { 38 | _clearColor = MTLClearColorMake(0, 0, 0, 1); 39 | _contentOffset = 0; 40 | _device = view.device; 41 | [self _loadMetalWithView:view]; 42 | [self _loadAssets]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (void)_loadMetalWithView:(nonnull MTKView *)view; 49 | { 50 | /// Load Metal state objects and initialize renderer dependent view properties 51 | 52 | view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; 53 | view.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; 54 | view.sampleCount = 1; 55 | 56 | _mtlVertexDescriptor = [[MTLVertexDescriptor alloc] init]; 57 | 58 | _mtlVertexDescriptor.attributes[VertexAttributePosition].format = MTLVertexFormatFloat3; 59 | _mtlVertexDescriptor.attributes[VertexAttributePosition].offset = 0; 60 | _mtlVertexDescriptor.attributes[VertexAttributePosition].bufferIndex = BufferIndexMeshPositions; 61 | 62 | _mtlVertexDescriptor.attributes[VertexAttributeTexcoord].format = MTLVertexFormatFloat2; 63 | _mtlVertexDescriptor.attributes[VertexAttributeTexcoord].offset = 0; 64 | _mtlVertexDescriptor.attributes[VertexAttributeTexcoord].bufferIndex = BufferIndexMeshGenerics; 65 | 66 | _mtlVertexDescriptor.layouts[BufferIndexMeshPositions].stride = 12; 67 | _mtlVertexDescriptor.layouts[BufferIndexMeshPositions].stepRate = 1; 68 | _mtlVertexDescriptor.layouts[BufferIndexMeshPositions].stepFunction = MTLVertexStepFunctionPerVertex; 69 | 70 | _mtlVertexDescriptor.layouts[BufferIndexMeshGenerics].stride = 8; 71 | _mtlVertexDescriptor.layouts[BufferIndexMeshGenerics].stepRate = 1; 72 | _mtlVertexDescriptor.layouts[BufferIndexMeshGenerics].stepFunction = MTLVertexStepFunctionPerVertex; 73 | 74 | id defaultLibrary = [_device newDefaultLibrary]; 75 | 76 | id vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; 77 | 78 | id fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"]; 79 | 80 | MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; 81 | pipelineStateDescriptor.label = @"MyPipeline"; 82 | pipelineStateDescriptor.sampleCount = view.sampleCount; 83 | pipelineStateDescriptor.vertexFunction = vertexFunction; 84 | pipelineStateDescriptor.fragmentFunction = fragmentFunction; 85 | pipelineStateDescriptor.vertexDescriptor = _mtlVertexDescriptor; 86 | pipelineStateDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat; 87 | pipelineStateDescriptor.depthAttachmentPixelFormat = view.depthStencilPixelFormat; 88 | pipelineStateDescriptor.stencilAttachmentPixelFormat = view.depthStencilPixelFormat; 89 | 90 | NSError *error = NULL; 91 | _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error]; 92 | if (!_pipelineState) 93 | { 94 | NSLog(@"Failed to created pipeline state, error %@", error); 95 | } 96 | 97 | MTLDepthStencilDescriptor *depthStateDesc = [[MTLDepthStencilDescriptor alloc] init]; 98 | depthStateDesc.depthCompareFunction = MTLCompareFunctionLess; 99 | depthStateDesc.depthWriteEnabled = YES; 100 | _depthState = [_device newDepthStencilStateWithDescriptor:depthStateDesc]; 101 | 102 | _dynamicUniformBuffer = [_device newBufferWithLength:sizeof(Uniforms) 103 | options:MTLResourceStorageModeShared]; 104 | 105 | _dynamicUniformBuffer.label = @"UniformBuffer"; 106 | 107 | _commandQueue = [_device newCommandQueue]; 108 | } 109 | 110 | - (void)_loadAssets 111 | { 112 | /// Load assets into metal objects 113 | 114 | NSError *error; 115 | 116 | MTKMeshBufferAllocator *metalAllocator = [[MTKMeshBufferAllocator alloc] 117 | initWithDevice: _device]; 118 | 119 | MDLMesh *mdlMesh = [MDLMesh newBoxWithDimensions:(vector_float3){2, 2, 2} 120 | segments:(vector_uint3){2, 2, 2} 121 | geometryType:MDLGeometryTypeTriangles 122 | inwardNormals:NO 123 | allocator:metalAllocator]; 124 | 125 | MDLVertexDescriptor *mdlVertexDescriptor = 126 | MTKModelIOVertexDescriptorFromMetal(_mtlVertexDescriptor); 127 | 128 | mdlVertexDescriptor.attributes[VertexAttributePosition].name = MDLVertexAttributePosition; 129 | mdlVertexDescriptor.attributes[VertexAttributeTexcoord].name = MDLVertexAttributeTextureCoordinate; 130 | 131 | mdlMesh.vertexDescriptor = mdlVertexDescriptor; 132 | 133 | _mesh = [[MTKMesh alloc] initWithMesh:mdlMesh 134 | device:_device 135 | error:&error]; 136 | 137 | if(!_mesh || error) 138 | { 139 | NSLog(@"Error creating MetalKit mesh %@", error.localizedDescription); 140 | } 141 | 142 | MTKTextureLoader* textureLoader = [[MTKTextureLoader alloc] initWithDevice:_device]; 143 | 144 | NSDictionary *textureLoaderOptions = 145 | @{ 146 | MTKTextureLoaderOptionTextureUsage : @(MTLTextureUsageShaderRead), 147 | MTKTextureLoaderOptionTextureStorageMode : @(MTLStorageModePrivate) 148 | }; 149 | 150 | _colorMap = [textureLoader newTextureWithName:@"ColorMap" 151 | scaleFactor:1.0 152 | bundle:nil 153 | options:textureLoaderOptions 154 | error:&error]; 155 | 156 | if(!_colorMap || error) 157 | { 158 | NSLog(@"Error creating texture %@", error.localizedDescription); 159 | } 160 | } 161 | 162 | - (void)drawCubeAtIndex:(int)idx inView:(nonnull MTKView *)view 163 | { 164 | /// Update any game state before encoding renderint commands to our drawable 165 | Uniforms * uniforms = (Uniforms*)_dynamicUniformBuffer.contents; 166 | uniforms->projectionMatrix = _projectionMatrix; 167 | 168 | 169 | float cubeOffset = (idx)/(float)(kCubeCount - 1); 170 | float cubeX = kMinX + cubeOffset * (kMaxX - kMinX); 171 | vector_float3 rotationAxis = { 172 | (idx == 0) ? 1 : 0, 173 | (idx == 1) ? 1 : 0, 174 | (idx == 2) ? 1 : 0 175 | }; 176 | matrix_float4x4 cubeRotate = matrix4x4_rotation(_rotation[idx], rotationAxis);; 177 | matrix_float4x4 cubeTranslate = matrix4x4_translation(cubeX, 0.0, 0.0); 178 | 179 | matrix_float4x4 modelMatrix = matrix_multiply(cubeTranslate, cubeRotate); 180 | 181 | float viewOffset = _contentOffset/view.bounds.size.width; 182 | float viewX = (kMinX + viewOffset * (kMaxX - kMinX)) * -1; // 183 | matrix_float4x4 viewMatrix = matrix4x4_translation(viewX, 0.0, -8.0); 184 | 185 | uniforms->modelViewMatrix = matrix_multiply(viewMatrix, modelMatrix); 186 | 187 | _rotation[idx] += .01; 188 | 189 | id commandBuffer = [_commandQueue commandBuffer]; 190 | commandBuffer.label = @"MyCommand"; 191 | 192 | /// Delay getting the currentRenderPassDescriptor until absolutely needed. This avoids 193 | /// holding onto the drawable and blocking the display pipeline any longer than necessary 194 | MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor; 195 | if (idx == 0) { 196 | renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; 197 | renderPassDescriptor.colorAttachments[0].clearColor = _clearColor; 198 | } else { 199 | renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionLoad; 200 | } 201 | renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; 202 | 203 | /// Final pass rendering code here 204 | 205 | id renderEncoder = 206 | [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; 207 | renderEncoder.label = @"MyRenderEncoder"; 208 | 209 | [renderEncoder pushDebugGroup:@"DrawBox"]; 210 | 211 | [renderEncoder setFrontFacingWinding:MTLWindingCounterClockwise]; 212 | [renderEncoder setCullMode:MTLCullModeBack]; 213 | [renderEncoder setRenderPipelineState:_pipelineState]; 214 | [renderEncoder setDepthStencilState:_depthState]; 215 | 216 | [renderEncoder setVertexBuffer:_dynamicUniformBuffer 217 | offset:0 218 | atIndex:BufferIndexUniforms]; 219 | 220 | [renderEncoder setFragmentBuffer:_dynamicUniformBuffer 221 | offset:0 222 | atIndex:BufferIndexUniforms]; 223 | 224 | for (NSUInteger bufferIndex = 0; bufferIndex < _mesh.vertexBuffers.count; bufferIndex++) 225 | { 226 | MTKMeshBuffer *vertexBuffer = _mesh.vertexBuffers[bufferIndex]; 227 | if((NSNull*)vertexBuffer != [NSNull null]) 228 | { 229 | [renderEncoder setVertexBuffer:vertexBuffer.buffer 230 | offset:vertexBuffer.offset 231 | atIndex:bufferIndex]; 232 | } 233 | } 234 | 235 | [renderEncoder setFragmentTexture:_colorMap 236 | atIndex:TextureIndexColor]; 237 | 238 | for(MTKSubmesh *submesh in _mesh.submeshes) 239 | { 240 | [renderEncoder drawIndexedPrimitives:submesh.primitiveType 241 | indexCount:submesh.indexCount 242 | indexType:submesh.indexType 243 | indexBuffer:submesh.indexBuffer.buffer 244 | indexBufferOffset:submesh.indexBuffer.offset]; 245 | } 246 | 247 | [renderEncoder popDebugGroup]; 248 | 249 | [renderEncoder endEncoding]; 250 | 251 | if (idx == (kCubeCount - 1)) { 252 | [commandBuffer presentDrawable:view.currentDrawable]; 253 | } 254 | 255 | [commandBuffer commit]; 256 | [commandBuffer waitUntilCompleted]; 257 | } 258 | 259 | - (void)drawInMTKView:(nonnull MTKView *)view 260 | { 261 | /// Per frame updates here 262 | for (int idx = 0; idx < kCubeCount; ++idx) { 263 | [self drawCubeAtIndex:idx inView:view]; 264 | } 265 | } 266 | 267 | - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size 268 | { 269 | /// Respond to drawable size or orientation changes here 270 | 271 | float aspect = size.width / (float)size.height; 272 | _projectionMatrix = matrix_perspective_right_hand(65.0f * (M_PI / 180.0f), aspect, 0.1f, 100.0f); 273 | } 274 | 275 | #pragma mark Matrix Math Utilities 276 | 277 | matrix_float4x4 matrix4x4_translation(float tx, float ty, float tz) 278 | { 279 | return (matrix_float4x4) {{ 280 | { 1, 0, 0, 0 }, 281 | { 0, 1, 0, 0 }, 282 | { 0, 0, 1, 0 }, 283 | { tx, ty, tz, 1 } 284 | }}; 285 | } 286 | 287 | static matrix_float4x4 matrix4x4_rotation(float radians, vector_float3 axis) 288 | { 289 | axis = vector_normalize(axis); 290 | float ct = cosf(radians); 291 | float st = sinf(radians); 292 | float ci = 1 - ct; 293 | float x = axis.x, y = axis.y, z = axis.z; 294 | 295 | return (matrix_float4x4) {{ 296 | { ct + x * x * ci, y * x * ci + z * st, z * x * ci - y * st, 0}, 297 | { x * y * ci - z * st, ct + y * y * ci, z * y * ci + x * st, 0}, 298 | { x * z * ci + y * st, y * z * ci - x * st, ct + z * z * ci, 0}, 299 | { 0, 0, 0, 1} 300 | }}; 301 | } 302 | 303 | matrix_float4x4 matrix_perspective_right_hand(float fovyRadians, float aspect, float nearZ, float farZ) 304 | { 305 | float ys = 1 / tanf(fovyRadians * 0.5); 306 | float xs = ys / aspect; 307 | float zs = farZ / (nearZ - farZ); 308 | 309 | return (matrix_float4x4) {{ 310 | { xs, 0, 0, 0 }, 311 | { 0, ys, 0, 0 }, 312 | { 0, 0, zs, -1 }, 313 | { 0, 0, nearZ * zs, 0 } 314 | }}; 315 | } 316 | 317 | @end 318 | -------------------------------------------------------------------------------- /MetalScroller/ShaderTypes.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | // 7 | // Header containing types and enum constants shared between Metal shaders and Swift/ObjC source 8 | // 9 | #ifndef ShaderTypes_h 10 | #define ShaderTypes_h 11 | 12 | #ifdef __METAL_VERSION__ 13 | #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type 14 | #define NSInteger metal::int32_t 15 | #else 16 | #import 17 | #endif 18 | 19 | #include 20 | 21 | typedef NS_ENUM(NSInteger, BufferIndex) 22 | { 23 | BufferIndexMeshPositions = 0, 24 | BufferIndexMeshGenerics = 1, 25 | BufferIndexUniforms = 2 26 | }; 27 | 28 | typedef NS_ENUM(NSInteger, VertexAttribute) 29 | { 30 | VertexAttributePosition = 0, 31 | VertexAttributeTexcoord = 1, 32 | }; 33 | 34 | typedef NS_ENUM(NSInteger, TextureIndex) 35 | { 36 | TextureIndexColor = 0, 37 | }; 38 | 39 | typedef struct 40 | { 41 | matrix_float4x4 projectionMatrix; 42 | matrix_float4x4 modelViewMatrix; 43 | } Uniforms; 44 | 45 | #endif /* ShaderTypes_h */ 46 | 47 | -------------------------------------------------------------------------------- /MetalScroller/Shaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | // File for Metal kernel and shader functions 7 | 8 | #include 9 | #include 10 | 11 | // Including header shared between this Metal shader code and Swift/C code executing Metal API commands 12 | #import "ShaderTypes.h" 13 | 14 | using namespace metal; 15 | 16 | typedef struct 17 | { 18 | float3 position [[attribute(VertexAttributePosition)]]; 19 | float2 texCoord [[attribute(VertexAttributeTexcoord)]]; 20 | } Vertex; 21 | 22 | typedef struct 23 | { 24 | float4 position [[position]]; 25 | float2 texCoord; 26 | } ColorInOut; 27 | 28 | vertex ColorInOut vertexShader(Vertex in [[stage_in]], 29 | constant Uniforms & uniforms [[ buffer(BufferIndexUniforms) ]]) 30 | { 31 | ColorInOut out; 32 | 33 | float4 position = float4(in.position, 1.0); 34 | out.position = uniforms.projectionMatrix * uniforms.modelViewMatrix * position; 35 | out.texCoord = in.texCoord; 36 | 37 | return out; 38 | } 39 | 40 | fragment float4 fragmentShader(ColorInOut in [[stage_in]], 41 | constant Uniforms & uniforms [[ buffer(BufferIndexUniforms) ]], 42 | texture2d colorMap [[ texture(TextureIndexColor) ]]) 43 | { 44 | constexpr sampler colorSampler(mip_filter::linear, 45 | mag_filter::linear, 46 | min_filter::linear); 47 | 48 | half4 colorSample = colorMap.sample(colorSampler, in.texCoord.xy); 49 | 50 | return float4(colorSample); 51 | } 52 | -------------------------------------------------------------------------------- /MetalScroller/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Sidharth Juyal on 13/05/2021. 3 | // Copyright © 2021 ___ORGANIZATIONNAME___. All rights reserved. 4 | // 5 | 6 | #import 7 | #import "AppDelegate.h" 8 | 9 | int main(int argc, char * argv[]) { 10 | NSString * appDelegateClassName; 11 | @autoreleasepool { 12 | // Setup code that might create autoreleased objects goes here. 13 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 14 | } 15 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mixing Metal with UIScrollView 2 | 3 | About mixing `UIScrollView` with `MTKView`. This is the trick I learned from [WWDC 2012 Enhancing User Experience with Scroll Views](https://developer.apple.com/videos/play/wwdc2012/223/) session that talks about mixing `UIScrollView` with OpenGL. The idea is to simply use the `MTKView` to render whatever metal content we would like and then use the `UIScrollView` to provide with the scrolling effect. The benefit of using `UIScrollView` is that we get exactly the same dragging and bounciness behavior that iOS users expect. 4 | 5 | So, for example let's say we have a `MTKView` that renders 3 cubes on screen and at any given time only of the cube is visible on screen. To prepare for scrolling we need to expose a `CGPoint scrollOffset` property that can scroll the content. 6 | 7 | ```objc 8 | @interface CubeView : MTKView 9 | @property (nonatomic) CGPoint scrollOffset; 10 | @end 11 | ``` 12 | 13 | On top of it we also need to handle touch events for other things such as changing the background color at tap. 14 | 15 | ```objc 16 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 17 | { 18 | [self _randomizeColor]; 19 | } 20 | ``` 21 | 22 | Now since in our app, we only ever scroll horizontally, we can simply forward the `x` value to the `Renderer` 23 | 24 | ```objc 25 | - (void)setScrollOffset:(CGPoint)scrollOffset 26 | { 27 | _renderer.contentOffset = scrollOffset.x; 28 | } 29 | ``` 30 | 31 | Then in the `Renderer` we can use this `contentOffset` value to calculate the *view matrix* 32 | 33 | ```objc 34 | matrix_float4x4 modelMatrix = ... 35 | 36 | float viewOffset = _contentOffset/view.bounds.size.width; 37 | float viewX = (kMinX + viewOffset * (kMaxX - kMinX)) * -1; 38 | matrix_float4x4 viewMatrix = matrix4x4_translation(viewX, 0.0, -8.0); 39 | 40 | uniforms->modelViewMatrix = matrix_multiply(viewMatrix, modelMatrix); 41 | ``` 42 | 43 | We need to multiply with `-1` because we need to move the content in the opposite direction of the scroll. Remember the `contentOffset` gives a increasing `+ve` value from `0` as we go towards right, while the content is actually moving left. 44 | 45 | Next we need to actually add the `UIScrollView`. The simplest way could be to just add a `UIScrollView` on top of `CubeView` and use the `UIScrollViewDelegate` to forward the `contentOffset`. 46 | 47 | ```objc 48 | - (void)viewDidLoad 49 | { 50 | [super viewDidLoad]; 51 | 52 | _cubeView = (CubeView *)self.view; 53 | 54 | CGRect cubeVwFrame = [_cubeView bounds]; 55 | CGSize contentSize = CGSizeMake(CGRectGetWidth(cubeVwFrame) * 2, CGRectGetHeight(cubeVwFrame)); 56 | UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:cubeVwFrame]; 57 | scrollView.contentSize = contentSize; 58 | scrollView.showsHorizontalScrollIndicator = NO; 59 | scrollView.delegate = self; 60 | [self.view addSubview:scrollView]; 61 | } 62 | 63 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 64 | { 65 | _cubeView.scrollOffset = scrollView.contentOffset; 66 | } 67 | ``` 68 | 69 | But remember we would like to handle the touch events on the `CubeView` for updating the background color. So this solution won't work since `UIScrollView` is going to consume all the touch events. The solution then is to not let the `UIScrollView` be part of the responder chain but still be able to use the gesture recognizers of `UIScrollView`. One way to achieve this is by using a placeholder view. 70 | 71 | ```objc 72 | - (void)viewDidLoad 73 | { 74 | [super viewDidLoad]; 75 | 76 | _cubeView = (CubeView *)self.view; 77 | 78 | CGRect cubeVwFrame = [_cubeView bounds]; 79 | CGSize contentSize = CGSizeMake(CGRectGetWidth(cubeVwFrame) * 2, CGRectGetHeight(cubeVwFrame)); 80 | UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:cubeVwFrame]; 81 | scrollView.contentSize = contentSize; 82 | scrollView.showsHorizontalScrollIndicator = NO; 83 | scrollView.delegate = self; 84 | scrollView.hidden = YES; // remove from responder chain 85 | [self.view addSubview:scrollView]; 86 | 87 | UIView *dummyView = [[UIView alloc] initWithFrame:scrollView.frame]; 88 | [dummyView addGestureRecognizer:scrollView.panGestureRecognizer]; 89 | [self.view addSubview:dummyView]; 90 | } 91 | ``` 92 | 93 | With this in place, the `UIScrollView` would not be part of the responder chain but still provide us with the `UIPanGestureRecognizer` and the app should behave as we expect. 94 | 95 | ![success](https://user-images.githubusercontent.com/213683/118399153-12960480-b65c-11eb-82f2-3a7bd6038738.gif) 96 | --------------------------------------------------------------------------------