Moving from OpenGL to Metal
In this Metal tutorial, you’ll learn how to move your project from OpenGL to Apple’s 3D graphics API: Metal. By Andrew Kharchyshyn.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Moving from OpenGL to Metal
25 mins
- OpenGL ES vs. Metal
- Understanding Conceptual Differences
- Getting Started
- Integrating Metal
- Switching From OpenGL
- Setting Up the Storyboard
- Setting Up Metal
- Basic Drawing
- Drawing Primitives
- Data Buffers
- Adding Shaders
- Writing a Vertex Shader
- Writing a Fragment Shader
- Hooking up the Shaders to the Pipeline
- Matrices
- Projection Matrix
- Matrices in Shaders
- Making it Spin
- Where to Go From Here?
Matrices
In order to manipulate the scene, you need to pass the projection and model-view matrices to the GPU. The projection matrix allows you to manipulate the perception of the scene to make closer objects appear bigger than farther ones. The model view matrix allows you to manipulate the position, rotation, and scale of an object or the whole scene.
In order to use those matrices, you’ll create a new struct. Open Vertex.swift. At the top of the file, add:
struct SceneMatrices {
var projectionMatrix: GLKMatrix4 = GLKMatrix4Identity
var modelviewMatrix: GLKMatrix4 = GLKMatrix4Identity
}
Note that you’ll still use GLKMatrix4
. This part of GLKit is not deprecated, so you can use it for matrices in Metal.
Now, open ViewController.swift, and add two new properties:
private var sceneMatrices = SceneMatrices()
private var uniformBuffer: MTLBuffer!
Then, go to draw(in:)
, and right before:
renderEncoder.setRenderPipelineState(pipelineState)
Add:
// 1
let modelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, -6.0)
sceneMatrices.modelviewMatrix = modelViewMatrix
// 2
let uniformBufferSize = MemoryLayout.size(ofValue: sceneMatrices)
uniformBuffer = metalDevice.makeBuffer(
bytes: &sceneMatrices,
length: uniformBufferSize,
options: .storageModeShared)
// 3
renderEncoder.setVertexBuffer(uniformBuffer, offset: 0, index: 1)
Here’s what the code above does:
- Creates a matrix to shift the object backwards by 6 units, to make it look smaller.
- Creates a uniform buffer like you did with vertex buffer before, but with matrices data.
- Hooks the uniform buffer to the pipeline and sets its identifier to 1.
Projection Matrix
While you’re still in ViewController.swift, inside mtkView(_:drawableSizeWillChange:)
, add the following:
let aspect = fabsf(Float(size.width) / Float(size.height))
let projectionMatrix = GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(65.0),
aspect,
4.0,
10.0)
sceneMatrices.projectionMatrix = projectionMatrix
This code creates a projection matrix based on the aspect ratio of the view. Then, it assigns it to the scene’s projection matrix.
With this in place, your square will now look square and not stretched out to the whole screen. :]
Matrices in Shaders
You’re almost there! Next, you’ll need to receive matrices data in shaders. Open Shaders.metal. At the very top, add a new struct:
struct SceneMatrices {
float4x4 projectionMatrix;
float4x4 viewModelMatrix;
};
Now, replace the basic_vertex
function with the following:
vertex VertexOut basic_vertex(
const device VertexIn* vertex_array [[ buffer(0) ]],
const device SceneMatrices& scene_matrices [[ buffer(1) ]], // 1
unsigned int vid [[ vertex_id ]]) {
// 2
float4x4 viewModelMatrix = scene_matrices.viewModelMatrix;
float4x4 projectionMatrix = scene_matrices.projectionMatrix;
VertexIn v = vertex_array[vid];
// 3
VertexOut outVertex = VertexOut();
outVertex
.computedPosition = projectionMatrix * viewModelMatrix * float4(v.position, 1.0);
outVertex.color = v.color;
return outVertex;
}
Here’s what has changed:
- Receives matrices as a parameter inside the vertex shader.
- Extracts the view model and projection matrices.
- Multiplies the position by the projection and view model matrices.
Build and run the app. You should see this:
W00t! A square!
Making it Spin
In the OpenGL implementation, GLViewController
provided lastUpdateDate
which would tell you when the last render was performed. In Metal, you’ll have to create this yourself.
First, in ViewController, add a new property:
private var lastUpdateDate = Date()
Then, go to draw(in: )
, and just before:
commandBuffer.present(drawable)
Add the following code:
commandBuffer.addCompletedHandler { _ in
self.lastUpdateDate = Date()
}
With this in place, when a frame drawing completes, it updates lastUpdateDate
to the current date and time.
Now, it’s time to spin! In draw(in:)
, replace:
let modelViewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -6.0)
With:
var modelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, -6.0)
let timeSinceLastUpdate = lastUpdateDate.timeIntervalSince(Date())
// 1
rotation += 90 * Float(timeSinceLastUpdate)
// 2
modelViewMatrix = GLKMatrix4Rotate(
modelViewMatrix,
GLKMathDegreesToRadians(rotation), 0, 0, 1)
This increments the rotation property by an amount proportional to the time between the last render and this render. Then, it applies a rotation around the Z-axis to the model-view matrix.
Build and run the app. You’ll see the cube spinning. Success!
Where to Go From Here?
Download the final project for this tutorial using the Download Materials button at the top or bottom of the tutorial.
Congrats! You’ve learned a ton about the Metal API! Now you understand some of the most important concepts in Metal, such as shaders, devices, command buffers, and pipelines, and you have some useful insights into the differences between OpenGL and Metal.
For more, be sure to check out these great resources from Apple:
- Apple’s Metal for OpenGL Developers is a great video for anyone with OpenGL experience.
- Apple’s Metal for Developers page has links to documentation, videos and sample code.
- Apple’s Metal Programming Guide.
- Apple’s Metal Shading Language Guide.
- Metal videos from WWDC 2014.
- Metal videos from WWDC 2015.
- Metal videos from WWDC 2016.
- Metal videos from WWDC 2017.
- Metal videos from WWDC 2018.
You also might enjoy this Beginning Metal course, which covers these concepts in detail with video tutorials.
If reading is more your style, check out the Metal Getting Started tutorial. For an entire book on the subject, see Metal by Tutorials.
I hope you’ve enjoyed this tutorial! If you have any comments or questions, please join the forum discussion below!