To use something effectively, it’s important to understand how it works. In this article, I’ll give a brief overview of how Metal operates and what you need to know to get started. Honestly, I wish I had this knowledge when Metal first launched, as it would’ve made my transition from OpenGL much smoother. So, let’s dive in (for more fun I used illustrations from Factorio)!
Metal includes many different entities, most of which are represented by protocols. Their actual implementations are managed by the system and can vary slightly depending on the chipset, platform, or whether you’re in release or debug mode. Technically, you could write your own implementation if needed (but I never met any cases for it). For now, let’s focus on the most commonly used ones:

This represents the hardware where all computations take place (rendering is a form of computation too). It’s also where we store our objects and resources.

A block of memory used to store your data (meshes, parameters, etc). While there are various nuances regarding optimization, memory sharing, and access, I’ll cover those details in future articles. Buffers are allocated on GPU side and you operate just references to them.

Essentially, an image stored in memory. It shares many similarities with a buffer, but includes additional metadata like texture size, format, and other attributes. Same as buffers, textures are allocated on GPU side amd operated through references.

This defines how your function or group of functions (including shaders, which are functions too) are executed on the GPU to process your data. (Sorry, I couldn’t find a better illustration than the manipulator, but it still gets the point across!)

The command queue is responsible for sending commands from the CPU to the GPU. Think of it like a railway system, where the CPU is one station, the GPU is the other, and the command queue serves as the tracks connecting them.

You can’t send individual commands directly; they need to be grouped together. In our analogy, think of a command buffer as a train: you attach different cars (commands), load them up, and send them off to the GPU.

This represents a specific action we need the GPU to perform, such as compute, blit, or render (which we’ll cover in more detail in the next series). In our analogy, it’s like a train car where we load several actions of the same type, as long as there are no conflicts between the targets. All the necessary resources are also loaded into this car.

MTLDeviceMTLCommandQueueMTLCommandBufferMTLRenderCommandEncoderMTLRenderCommandEncoder for rendering into the textureAs you can see, it’s quite simple if you focus on the principles rather than the boilerplate code (which isn’t too heavy anyway). You might ask, how do you see the results? You can download the contents of your buffers or textures (if allowed), so there’s no need for views or surfaces. This enables you to perform off-screen computing or rendering, even in a console application.