Skip to content

Instantly share code, notes, and snippets.

@rmirabelli
Created March 9, 2018 17:28
Show Gist options
  • Save rmirabelli/e22ab593c856f905ac1dc5c6dda5d255 to your computer and use it in GitHub Desktop.
Save rmirabelli/e22ab593c856f905ac1dc5c6dda5d255 to your computer and use it in GitHub Desktop.

The Joy of Swift Metal

Introduction: I love plygrounds

For the past several years, I've been spoiled -- as have many developers -- by the lack of an edit/compile/run cycle when working in Swift Playgrounds. Swift Playgrounds give us a chance to easily write quick code to test out concepts without building up a full application, deploying to device or simulator, and observing the results. I've used Swift Playgrounds to better understand many technologies, including:

  • Swift language concepts
  • CoreAnimation
  • SpriteKit
  • SceneKit

One area that had been eluding me has been working in Metal in a Swift Playground. Metal is Apple's low-level graphics system, built to take advantage of their own hardware and provide great performance. I rather doubted that there'd be practical output to working directly in Metal-- SceneKit does a great job for me-- but I still felt the need to scratch that itch. And thus was another playground adventure begun.

Configuring the playground

When you're creating a playground for Metal development, you'll need to set it up as a macOS playground. iOS playgrounds are not eligible, as the simulator doesn't provide the direct GPU access we need for Metal development.

We need to import a few frameworks. We need access to Cocoa, in order to handle our macOS view. We need Metal in order to, well, work with Metal. Lastly, we need PlaygroundSupport in order to show the Metal view.

https://gist.github.com/48f06777233f02a6e4aba0a440e804e3

Some of the code that we use implements Swift's try/catch mechanism, so we'll just wrap everything up inside a block: https://gist.github.com/47f3a1d30ebea60337a3adf6a0cd26dd

The basics of a metal program

Creating a Metal device

The simplest step, which underpins all of the other steps, is to create a metal device. This returns a handle to the metal device for the system. There aren't any options, choices, or really anything to discuss. You need a device, so get it. https://gist.github.com/48c33504eeb0d4951d1aca582911a242

Setting up a memory buffer for vertex data

We need some data to draw with. Metal works best with standard Float data. We're going to set up to use a default viewport, which extends in the cartesian plane from -1 to 1 in both X and Y axes, and keep all of our points at zero on the Z axis.

After we configure a triangle, the next step is to make that array of vertices available to our device. We do this by telling the device to create a buffer from our array of bytes, and we need to provide a size for this buffer. Importantly, to compute the size, do not use the size of a data element; this is not guaranteed to meet up with actual physical memory. Computations need to be made by using the stride.

https://gist.github.com/2a5b8b9bda2d6bfa8e5bcd9fe09a1690

Creating a render pipeline

Now that we have a device & some data, it's time to create our render pipeline, which is to say the code through which all of our future render commands will be funneled for drawing onscreen. The first step is the simplest, we start by ceating a desciptor which will configue the ultimate pipeline. Think of it as a builder stucture. https://gist.github.com/b63ca97503d842e499539de3259ed81a

Runtime-compiled shaders

This is where things get interesting. In Metal, we typically create a .metal shader file, which is separately compiled in the project then loaded at runtime. This doesn't lend itself to effective playground use, though. In a playground, we want to be able to modify code at will, and that includes shader code. Thankfully, we have a way to do this. You wouldn't want to dynamically compile shader code at runtime in a real app, of course. Converseley, you don't want to statically compile shader code in a playground. https://gist.github.com/ee99a6073a80ce493bebf337bbd46ef1

This consists of two shaders, a vertex shader and a fragment shader. A vertex shader provides instructions on how to transform all vertices before submission to the renderer. In this case, we're simply passing our vertex inforrmation unchanged. A fragment shader, often referred to as a pixel shader, is responsible for computing the final appearance of any given pixel. In this case, I'm simply returning a constant color for any given pixel.

Finalizing the pipeline

Once you have your shaders, you can provide them to the pipeline descriptor. We set up the vertexFunction and fragmentFunction, as well as the output pixel format from the pipeline. After completing the descriptor setup, we finalize that by calling makeRenderPipelineState. https://gist.github.com/3ba3d6690d10aea33ca0bc052a50ef87

Setting up a view

For an iOS dev, there are some steps we're not familiar with to create a view that can be used in a playground. On iOS, each UIView has a layer, and we would add our metal layer as a sublayer to the existing UIView layer. NSViews in macOS do not have a layer unless we delibarately set one. This is not particularly complex at all, but it's just subtly different enough that it's worth mentioning. https://gist.github.com/262d1d457c88c1042508422d31bf3e2d

Prepare a command buffer

We're almost ready to draw into our layer. Next, we have to set up a buffer for our drawing commands, and prepare a texture that we'll render into. https://gist.github.com/2fe2642a39b8462e112dfbc5400f6f28

Create a buffer of render commands, and show

Finally, after all of that setup, we can prepare to draw a triangle based around our vertex buffer. Then, we can show the buffer we've created. In a real app, this portion would typically be found within a display link, repeated each frame (with the drawable being obtained each frame), but for this project a single pass at drawing will be sufficient. https://gist.github.com/f1121bad8bfe0840b2f932ffd7f0d30f

A glorious triangle!

Our reward for all of this code (and it's a fair amount)? A glorious, single triangle on a red background. Not much to look at, but it's a place we can grow from!

Afterward

This article really isn't meant to provide an in-depth understanding of Metal. What I'm more interested in is providing you with a starting point, from which you can begin your own exploration. That's the joy of playgrounds, and so I hope I've gotten you set up in a way that you can experiment and learn more for yourself. I'd love to hear about what you're learning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment