Metal Rendering Pipeline Tutorial

Take a deep dive through the rendering pipeline and create a Metal app that renders primitives on screen, in this excerpt from our book, Metal by Tutorials! By Marius Horga.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 5 of 5 of this article. Click here to view the first page.

6 – Framebuffer

As soon as fragments have been processed into pixels the Distributer unit sends them to the Color Writing unit. This unit is responsible for writing the final color in a special memory location called the framebuffer. From here, the view gets its colored pixels refreshed every frame. But does that means the color is written to the framebuffer while being displayed on the screen?

A technique called double-buffering is used to solve this situation. While the first buffer is being displayed on the screen, the second one is updated in the background. Then, the two buffers are swapped, and the second one is displayed on the screen while the first one is updated, and the cycle continues.

Whew! That was a lot of hardware information to take in. However, the code you’ve written is what every Metal renderer uses, and despite just starting out, you should begin to recognize the rendering process when you look at Apple’s sample code.

Build and run the app, and your app will render this red cube:

Notice how the cube is not square. Remember that Metal uses Normalized Device Coordinates (NDC) that is -1 to 1 on the X axis. Resize your window, and the cube will maintain a size relative to the size of the window.

Send Data to the GPU

Metal is all about gorgeous graphics and fast and smooth animation. As a next step, you’ll make your cube move up and down the screen. To do this, you’ll have a timer that updates every frame and the cube’s position will depend on this timer. The vertex function is where you update vertex positions so you’ll send the timer data to the GPU.

At the top of Renderer, add the timer property:

var timer: Float = 0

In draw(in:), just before:

renderEncoder.setRenderPipelineState(pipelineState)

add:

// 1
timer += 0.05
var currentTime = sin(timer)
// 2
renderEncoder.setVertexBytes(&currentTime, 
                              length: MemoryLayout<Float>.stride, 
                              index: 1)
  1. You add the timer to every frame. You want your cube to move up and down the screen, so you’ll use a value between -1 and 1. Using sin() is a great way to achieve this as sine values are always -1 to 1.
  2. If you’re only sending a small amount of data (less than 4kb) to the GPU, setVertexBytes(_:length:index:) is an alternative to setting up a MTLBuffer. Here, you set currentTime to be at index 1 in the buffer argument table.

In Shaders.metal, replace the vertex function with:

vertex float4 vertex_main(const VertexIn vertexIn [[ stage_in ]],
                          constant float &timer [[ buffer(1) ]]) {
  float4 position = vertexIn.position;
  position.y += timer;
  return position;
}

Here, your vertex function receives the timer as a float in buffer 1. You add the timer value to the y position and return the new position from the function.

Build and run the app, and you now have an animated cube!

With just a few bits of code, you’ve learned how pipelines work and you even added a little animation.

Where to Go From Here?

If you want to check out the completed project for this tutorial, you can find it in the final directory of the downloads for this tutorial.

If you enjoyed what you learned in this tutorial, why not check out our Metal by Tutorials book, available on our store?

This book will introduce you to low-level graphics programming in Metal — Apple’s framework for programming on the graphics processing unit (GPU). As you progress through this book, you’ll learn many of the fundamentals that go into making a game engine and gradually put together your own engine.

Once your game engine is complete, you’ll be able to put together 3D scenes and program your own simple 3D games. Because you’ll have built your 3D game engine from scratch, you’ll be able to customize every aspect of what you see on your screen.

But beyond the technical definition, Metal is the most appropriate way to use the GPU’s parallel processing power to visualize data or solve numerical challenges. It’s also tailored to be used for machine learning, image/video processing or, as this book describes, graphics rendering.

This is a perfect resource for intermediate Swift developers interested in learning 3D graphics or gaining a deeper understanding of how game engines work.

To celebrate the launch of the book, it’s currently on sale as part of our Game On book launch event. But don’t wait too long, as this deal is only good until Friday, June 8th!

If you have any questions or comments on this tutorial, feel free to join the discussion below!