Swift 3 and Metal
Nov 17, 2016
As a follow-up to the previous post, perhaps it’s also helpful to explain the easiest way to draw to a Cocoa view using Metal rather than OpenGL1. And as a bonus, I also explain how to select OpenGL or Metal rendering on the fly.
Some background
Apple has never been diligent in keeping up with new OpenGL versions (or the latest graphics cards for that matter), but the situation has reached a pretty bad point. Even the newest hardware supports only six-year-old OpenGL 4.1. By comparison, Metal is under constant development and has tons of new features announced every WWDC. But perhaps for the same reason there’s a dearth of documentation and good, introductory level and up-to-date examples2.
Now, to be honest, I’d prefer to use something open and cross-platform like Vulkan, but it’s still a no-show on macOS (and I don’t expect it to change in the future). Or I could switch to Windows. Just kidding. I’d of course switch to Linux instead.
Drawing to an MTKView
By far the easiest way to draw to a Cocoa view is to subclass MTKView
, part of MetalKit and available from iOS 9 and El Capitan onwards. This is similar to NSOpenGLView
for OpenGL, and it means that most things are managed for you automatically. So, you probably need to set up a class like this:
Afterwards, create the init()
method and set up the Metal device, MTKView properties, command queue, render pipeline, shaders and depth stencil there. The same goes if you need textures or other stencils.
Finally, create the draw
method, which will be called either automatically every frame, automatically with a view notification or manually, depending on how you set up isPaused
and enableSetNeedsDisplay
.
Selecting Metal or OpenGL rendering on the fly
If you’re excited about the performance of Metal (like me) but want to support Mac hardware older than 2012 (like me), you probably want to select between custom subclasses of MTKView
and NSOpenGLView
on the fly. In that way, you can probably throw away the OpenGL code in a few years without major code rewrites.
For me, the first step was to isolate all OpenGL- and Metal-specific code into these two subclasses. After that, create a simple placeholder NSView in Interface Builder and kept a reference to it (here view
). Now, when your application loads you can do something like this:
Note a few things. If you want to support the newest Metal features as of 20163, you want to test for Sierra support. My application crashes on 10.11 even on Metal-compatible hardware4, so I’ve forced OpenGL on 10.11 or lower. The easiest way to test for Metal support is to check the return of MTLCreateSystemDefaultDevice()
.
Also, substituting a view on the fly is tricky business. Substituting only the view will not have any effect, so you need to remove it from its parent view (in this case splitView
, an instance of NSSplitView
) and re-add your subclass of MTKView
or NSOpenGLView
.
Finally, for this to work you need to use different initialisers in your MTKView
and NSOpenGLView
subclasses. These should receive the view’s frame and other variables rather than an instance of NSCoder
.
-
For my application, this resulted in a roughly 10x performance improvement over OpenGL. Your mileage may vary. ↩
-
The clear exception to this is Apple’s Adopting Metal 2016 WWDC sessions. See part 1 and part 2. Don’t forget to check out the sample code. ↩
-
Why is Apple not putting clear version numbers?! ↩
-
As does Apple’s own Adopting Metal Sample code… ↩