Exploring Apple's Metal Framework: A Beginner's Guide
By Luke
Apple’s Metal framework has been a game-changer in the world of 3D graphics on iOS and MacOS platforms. Since its introduction in 2014 and the subsequent deprecation of OpenGL, Metal has become the go-to framework for optimized graphics on Apple devices, especially with the advent of Apple Silicon and VisionOS. In this tutorial, we’ll dive into the basics of Metal using Swift in Xcode.
Getting Started with Xcode and Metal
Xcode is Apple’s integrated development environment (IDE) for creating applications on its various platforms. We’ll begin by setting up a playground in Xcode to experiment with Metal.
Creating a New Playground
- Open Xcode
- Go to File menu
- Select ‘New’ > ‘Playground’
We’ll be using Swift, Apple’s powerful and intuitive programming language. Don’t worry if you’re new to Swift; I’ll guide you through the tricky parts.
Importing Necessary Modules
To start, we need to import the necessary modules - PlaygroundSupport
and MetalKit
.
import PlaygroundSupport
import MetalKit
Accessing the GPU with Metal
Creating a Metal Device
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("GPU not available")
}
This code attempts to create a default Metal device. If unsuccessful, it triggers a fatal error, stopping execution.
Setting Up the Metal Kit View
let frame = CGRect(x: 0, y: 0, width: 480, height: 270)
let view = MTKView(frame: frame, device: device)
view.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1)
PlaygroundPage.current.liveView = view
Even though we set the clear color, it’s not visible on the view. That’s because we haven’t set any rendering commands yet.
Rendering with Metal
Command Queue and Buffers
We need a command queue to send work to the GPU. Add the code below after setting the clear color, ensuring the line PlaygroundPage.current.liveView = view
always stays at the end of the source file.
guard let commandQueue = device.makeCommandQueue() else {
fatalError("Could not create a command queue")
}
Configuring the Render
To render, we require a command buffer and a drawable object:
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let drawable = view.currentDrawable else {
fatalError("Failed to create command buffer or drawable")
}
Finalizing the Render
Render Command Encoder is what encodes a drawing command into the command buffer. We don’t need to add any commands to the encoder, because we have no geometry to render. The encoder will clear the view with the selected clear color by default. That’s why we call endEncoding()
immediately after creating the object.
if let renderPassDescriptor = view.currentRenderPassDescriptor,
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) {
renderEncoder.endEncoding()
}
Then it’s just a matter of commiting our buffer:
commandBuffer.present(drawable)
commandBuffer.commit()
Running this will now show the correct clear color.
Full Code
This tutorial introduced the basics of using Metal in Swift. We set up a simple Metal environment and understood how rendering works in Metal. Here’s the full code you can copy and paste to your Xcode Playground:
import PlaygroundSupport
import MetalKit
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("No GPU?")
}
// setting up the MetalKit View
let frame = CGRect(x: 0, y: 0, width: 450, height: 450)
let view = MTKView(frame: frame, device: device)
view.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1)
// queue, buffer, and render pass
guard let commandQueue = device.makeCommandQueue(),
let commandBuffer = commandQueue.makeCommandBuffer() else {
fatalError("Could not create a command buffer or queue")
}
guard let renderPassDescriptor = view.currentRenderPassDescriptor else {
fatalError("No default render pass descriptor")
}
// the rendering commands will go to the encoder
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
fatalError("Could not create a render encoder")
}
renderEncoder.endEncoding() // endEncoding() marks the end of the rendering command for the render pass
// selecting the destination to draw to
guard let drawable = view.currentDrawable else {
fatalError("Cannot select drawing destination")
}
commandBuffer.present(drawable)
commandBuffer.commit() // this sends the commands to the GPU and draws everything
PlaygroundPage.current.liveView = view
But wait, there’s more!
Check out my video tutorial, where I show how to set up this basic playground step-by-step:
If you want even more, here’s the full playlist with all my FREE tutorials related to Apple Metal:
And don’t forget to subscribe to my YouTube channel for more!