Last time we looked at the Metal Shading Language basics. Before we look into more advanced topics, I just thought now is a good time to revisit what we have learned so far, especially about the graphics pipeline which I admit, I might have gone too fast on that topic (thank you, anonymous reader, for your suggestion and valuable feedback!)

Lets look at the graphics pipeline in more detail, and I’ll start with a piece of history here. It all started about a decade ago, when shaders were introduced as a way for programmers to be able to influence the fixed pipeline that existed until then. At the same time, floating point support was also introduced for GPUs facilitating the birth of GPGPU (general-purpose computing on graphics processing units). As a consequence, the new programmable pipeline was significantly changed:

alt text

As you can see, the new pipeline now has two shader stages and that is where we can write our own customized code that the GPU will then run. The first part of a graphics program always runs on the CPU and is often called host code. Here is where most of the resource allocation happens, as well as staging the data transfer to and from the GPU. The most important part of the program, however, runs on the GPU. The two shaders go into a separate file with a .metal extension (in other GPGPU frameworks such as OpenCL it is named kernel code).

The pipeline starts with the CPU stage where the input is sent to the GPU in the form of vertices. They undergo transformation and per-vertex lighting. At this point the vertex shader can be used to manipulate the vertices prior to rasterization. After that, vertices undergo clipping and rasterization resulting in fragments. The fragment shader can then be run on each fragment before the pixel values are output to the framebuffer for display.

Now let’s look at Metal's own pipeline. We will refer back to the part 2 source code and we’ll mention line numbers to exemplify the concepts we touch. Building a Metal application is done in two stages. The first one is the Initialization stage:

alt text

The very first step is getting the device (line 19 in MetalView.swift). A device is the direct connection to the GPU driver and hardware; it’s the source we need to create all the other objects in Metal. The second initialization step is creating a command queue (line 40) which is our channel to submit work to the GPU. The third initialization step is creating the buffers, textures and other resources (lines 20-27). The newBufferWithBytes function will allocate a new block of shared memory, copy the provided pointer into it, and return a handle to that buffer. The fourth initialization step is creating the render pipeline (lines 28-37) which is a chain of steps that start with taking vertex data from one end and producing a rasterized image on the other end. The pipeline consists of two elements: the descriptor which holds the shader information and the pixel format, and the state which is built from the descriptor and contains the compiled shaders. The fifth (last) initialization step is creating a view. For us it was easier to inherit from MTKView (line 11) rather than creating a new CAMetalLayer and adding it as a subview.

Next, let’s look at the second stage of building a Metal application, the Drawing stage:

alt text

The very first step is getting the command buffer (line 40). All of the work that goes to the GPU will be enqueued into this buffer. We need the command queue from the previous stage to have a command buffer created. The second step is setting up a render pass (lines 38-39). A render pass descriptor tells Metal what actions to take while an image is being rendered. Configuring it requires that we specify what color textures are we rendering to (the currentDrawable texture). We need to also choose which color the screen will be cleared to before we draw any geometry. The third step is the actually drawing (lines 43-44). We specify the buffer where the vertices are stored and then the primitives we need drawn. The fourth and final step is to commit the command buffer (lines 46-47) to the GPU. When calling commit, the command buffer gets encoded, sent to the end of the command queue, and executed on the GPU when its time comes.

I hope this part of the tutorial brought more clarity to understanding general topics such as the graphics pipeline and the Metal pipeline. I look forward to returning back to more coding in the next part of this tutorial.

Until next time!