Metal By Example’s first post, in Swift

This awesome guy Warren Moore made a blog post seven months ago called Up and Running with Metal, Part 1: Clearing the Screen. It was very helpful to me. However, I don’t want to use Objective-C. I think it’s the main reason I never stuck with OS X and iOS development, and kept going back to Unity, with its Mono-C#, even though most of what I’ve enjoyed doing with Unity is at a lower level of abstraction than Unity is geared for (like making GLSL shaders and effects for this game). Spurred on by Warren’s talk on using Swift and Metal together, I decided to dive in.

Before checking out my version of the project (note that you’ll need at least Xcode 6.3, which is in beta as I write this), get familiar with Warren’s code. Then, follow along here, as I go through some of the thought processes that led to my changes.

 

Even with using only Swift, I had to do a surprising amount of work, to get the code to start to seem like something I’d feel happy enough reading – the Metal API currently feels like it was made for  a hodgepodge of languages. Here are the supporting files I felt a need to write:

Metal part 1 extensions

Note that I don’t know what I’m doing yet, when it comes to extending frameworks, in Swift. I tried making modules/frameworks/namespaces today, but obviously don’t understand how to do it properly yet. So right now, all of these files are in the app’s namespace; this works for now, but surely won’t continue to do so. If you know what I should actually be doing, I’d appreciate any info.

The following result is as clean as I currently know how to write. The emoji-laden version from the top of this post really is something I tried, but I think this is a little better, due to the ability to type it more easily, and because of decreased line height. Some people will take other issue with the emoji, but I like that it called attention to the code being sub-ideal, and not further-improvable without Apple’s intervention, and I don’t think it’s hard to understand (though I’m not sure if it’s English).

Note the I’m not using a redraw method, as Warren did. I did not feel that this was the right time to introduce the concept of redrawing, as only one drawing happens.

Namespaces

Prefixes, in lieu of namespaces, do not belong in Swift. So I’ll be using typealiases until Apple makes it unnecessary:

typealias Device = MTLDevice

The exception here is UIView; I don’t know enough, to know if I like the name UIView, so ignorance is bliss, for now.

Proxies?

Even with the prefix gone, I am not cool with MTLCreateSystemDefaultDevice().

When something has Create in its name, it probably should be transmogrified into an initializer. The problem with that is, we’re returned a MTLDevice, which is a protocol, not a class or struct. This kind of thing is really common with Metal. Ugh. Protocols suck, for this purpose.

Step 1 is to wrap protocols in objects:

protocol Proxy {
    typealias Original
    var original: Original {get}
}

struct DeviceProxy: Proxy {
    init() {original = MTLCreateSystemDefaultDevice()}
    let original: Device
}

let device = DeviceProxy().original

My hope is that, in the future, Apple will enable this to be reduced to

let device = Device()

The changes I wanted to make to this initial, simple function, propagated to the rest of the code.

Protocol Extension

In Swift, aside from global functions, we’ve got no way to add functionality to protocol instances, like we can with classes, structs, and enums. In C#, you can only extend protocols (their name being interfaces, in that language) with instance methods, but that’s much better than nothing.

Rock Biter

A method would be something. No, Swift gives us nothing.

 

There’s only one non-initializer protocol-related thing I took issue with, for this tiny project. The names of parameters do not belong in method names:

extension CommandBufferProxy {
    func present(drawable: Drawable) {original.presentDrawable(drawable)}
}

This wouldn’t be worth doing, but I know from experience that I’m going to need some way to add actually meaningful functionality, in the future, so I figured something out.

postfix func +<T: Proxy>(proxy: T) -> (T.Original, T) {
    return (proxy.original, proxy)
}

That allows me to access the actual command buffer, and an object that will make it easier to deal with:

let commandBuffer = CommandBufferProxy(device)+
commandBuffer.1.present(drawable)

commandBuffer.1.original is the same as commandBuffer.0

I’m not attached to the plus sign, but I think it’s decent. I’d prefer the gemini symbol, but it’s not typeable.

I think this is a slight improvement upon using .original, or not, depending on whether the functionality you need is owned by the original, or the proxy. My desire is to be able to remove the .0‘s and .1‘s, as Metal and Swift are improved.

Apple has casing wrong, for enums

layer.pixelFormat = .BGRA8Unorm

That bgra part should be lowercase. It’s a constant. Only associated type “intializers” should be in PascalCase. But, it’s impractical for me to extend all of Apple’s enums that I’ll want to use, with static properties that get the casing right, so I leave it.

Metal is confused about nouns and verbs

extension Layer {
    func getNextDrawable() -> Drawable {return nextDrawable()}
}

nextDrawable is a noun, but its parentheses tell us that it’s a predicate. To eliminate confusion, I would extend Layer (CAMetalLayer) with a property named nextDrawable, but I can’t, because of not being able to have a property named the same as a method. So, I do the next best thing.

OMFG

MTLRenderPassDescriptor is actually a class!!!! Let’s give it a decent initializer!

extension RenderPassDescriptor {
    convenience init(drawable: Drawable, clearColor: ClearColor) {
        self.init()
        colorAttachments[0].texture = drawable.texture
        colorAttachments[0].clearColor = clearColor
        colorAttachments[0].storeAction = .Store
        colorAttachments[0].loadAction = .Clear
    }
}

 

I also took on Part 2.

Leave a Reply

Your email address will not be published. Required fields are marked *