Bare Metal: Working with Metal and the Simulator

James Campbell
4 min readOct 28, 2014

--

Note: At WWDC 19, Apple released a GPU accelorated version of the simulator for iOS 13. You can still follow these instructions for iOS 12 and below but consider these instructions deprecated for future editions of iOS.

By now you would of heard of Apple’s new graphics API “Metal” which promises blazing performance by providing direct access to the GPU. It also improves the load time of games by pre-compiling shaders.

However there is a catch, not only does it only work on the latest Apple A7 and A8 Chips but any app using Metal Shaders cannot be compiled for the simulator. This has been a snag for many developers with most having to stick to device-based testing or removing shaders from the project when testing on the simulator.

However there is a workaround, via the new framework support Xcode provides. Today we will be learning how to build an app that takes advantage of shaders for metal and still builds without them for the simulator, ending the need to remove them from the project file.

This tutorial assumes you are familiar with the concepts of Cocoapods and Metal.

First you need to make sure you have Cocoapods installed, we will be using this to simplify the inclusion of the shaders in our project file, follow the instructions at http://cocoapods.org/ to install it onto your machine.

Start by creating a new framework project from xcode, we are going to name this “Shaders”.

Go to the “Shaders” target’s build settings, here you need to add a “BUILD_DIR” user defined setting, make sure you set it to equal “$(SRCROOT)”.

We also need to set “Per-configuration Build Products Path” to be “$(BUILD_DIR)/”. Now when you build the target you should find a framework in the folder for your new project.

Now all that is required is for us to add your “.metal” files to the project like normal, check that all of these files appear in your “Compile Sources” build phase.

Build again to make sure everything is in order and if it compiles successfully. If you open the framework, you should then see a “.metalib” file, this is a compile Metal library full of our shaders. Now we can move onto creating a podspec to import this library into our app.

We create our pod spec file like any other:

Pod::Spec.new do |s|
s.name = “Shaders”
s.version = “0.1.0"
s.summary = “A short description of Shaders.”
s.description = <<-DESC
An optional longer description of Shaders
DESC
s.homepage = “https://github.com/Unii-Ltd/Shaders"
s.license = ‘MIT’
s.author = { “James Campbell” => “james.campbell@unii.com” }
s.source = { :git => “https://github.com/<GITHUB_USERNAME>/Shaders.git", :tag => s.version.to_s }
# s.social_media_url = ‘https://twitter.com/<TWITTER_USERNAME>'
s.platform = :ios, ‘7.0'
s.requires_arc = true

Okay now we need to pull in the Metal library we just compiled.

 s.preserve_paths = ‘Shaders.framework/*’
s.resources = ‘Shaders.framework/*.metallib’
end

What we are doing is telling cocoa pods to extract the metal library out of the framework and include it into our project, where it should appear in the copy resources folder for the Shader pod.

When your app is compile Cocoapods will automatically copy it into your app bundle where your app can use it. Now, all we have to do is add this pod to the pod file for your app,

pod ‘Shaders’, :path => ‘/PathToShaders’

and finally before building for the simulator you will need to make sure you don’t include the Metal framework anywhere in your code, as otherwise Xcode will automatically pull in a dummy header which causes your build to fail with a message telling you that it isn’t supported on the simulator.

This can easily be fixed by wrapping your code up in a macro, like so:

#if !(TARGET_IPHONE_SIMULATOR)#import <Metal/Metal.h>#endif

This should prevent the headers for the Metal framework from being linked to your project. For devices you can simply check if Metal is supported by using the new supportsFeatureSet method on the MTLDevice Class.

MTLDevice *device = MTLCreateSystemDefaultDevice();//Check's the device supports at least the basic Metal commandsif (device && [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v1]) {//Metal code goes here} else {//Fallback goes here}

To get the above code to work with the simulator then you only need to add the following piece of code to your prefix header. This will cause MTLCreateSystemDefaultDevice to return nil causing the fallback to be used.

#if (TARGET_IPHONE_SIMULATOR)#define MTLCreateSystemDefaultDevice() nil#define MTLFeatureSet_iOS_GPUFamily1_v1 0@interface MTLDevice : NSObject- (BOOL)supportsFeatureSet☹NSUInteger)value;@end#endif

If you combine these together then your metal code will work anywhere ☺. You could go even further defining the device check as a macro itself, something like `HAS_METAL` but I have left that as an exercise for the reader.

I hope you find this tutorial useful, if you have any questions then you can find me on twitter at https://twitter.com/jcampbell_05.

The project for the shaders framework can be found here, https://github.com/jcampbell05/Shaders-iOS

--

--