Before writing any Metal code, this page walks you through cloning the project, understanding its structure, and the key concepts behind Metal rendering.

Prerequisites

Before diving into Metal, it’s helpful to have a solid foundation in key concepts.

  • Swift
  • Graphics Fundamentals
    • Understanding how graphics work under the hood helps you optimize performance and troubleshoot issues.
    • Resources:
      • Learn OpenGL β€” Great for understanding the basics of rendering pipelines.
      • Scratchapixel β€” In-depth tutorials on graphics algorithms.
  • Metal API
    • Metal is Apple’s low-level graphics framework, optimized for performance on Apple Silicon and GPUs. It abstracts hardware details while giving you fine-grained control over rendering pipelines.
      • Resources:
        • Metal Programming Guide β€” Official guide to Metal concepts and APIs.
        • Metal Shading Language Specification β€” The complete MSL language reference (PDF). Bookmark Β§2 (address spaces) and Β§5 (vertex/fragment attributes) β€” you’ll return to both constantly.
        • Metal Feature Set Tables β€” Per-device capability matrix: max texture size, buffer binding slots, supported pixel formats, and more.
  • Useful other resources:

Setting up the project

Clone the repository

git clone https://github.com/Fe0437/MetalTutorials.git

Launch Xcode and open the MetalTutorials.xcodeproj file to begin exploring the project structure and tutorials.

πŸ”„ Switching between tutorials

To switch between tutorials, open MTMetalTutorialsApp.swift and modify the content view by changing the function call in the WindowGroup:

@main
struct MetalTutorialsApp: App {
    var body: some Scene {
        WindowGroup {
            // substitute here to choose the tutorial
            MT1ContentView()   // ← change to MT2ContentView(), MT3ContentView(), …
        }
    }
}

Each MTxContentView wires up the corresponding MTKView and renderer for that tutorial.

Each tutorial folder is self-contained and builds on the previous one. The Metal shader namespaces (MT1::, MT2::, MT3::) keep everything clearly separated.


How Metal works

The Metal rendering loop

To understand each tutorial, follow this basic Metal rendering loop pattern:

flowchart TD
    subgraph setup["One-time setup"]
        direction TB
        A([App launch]) --> B[/"Create MTLDevice\nβ€” the GPU handle"/]
        B --> C[/"Create MTKView\nβ€” the Metal-backed window"/]
        C --> D[/"Assign MTKViewDelegate\nβ€” your renderer object"/]
        D --> E[/"Compile pipelines,\nallocate buffers & textures"/]
    end

    subgraph loop["Per-frame render loop  β†Ί"]
        direction TB
        F([draw called by MTKView]) --> G[/"new MTLCommandBuffer\nβ€” records this frame"/]
        G --> H[/"new MTLRenderCommandEncoder\nβ€” encodes draw calls"/]
        H --> I[["setVertexBuffer Β· setRenderPipelineState\nΒ· drawPrimitives / drawIndexedPrimitives"]]
        I --> J[/"endEncoding"/]
        J --> K[/"present drawable\nthen commit"/]
    end

    setup --> loop
    K -.->|next frame| F

Understanding this loop before diving into the tutorials will make the code much easier to follow.

UIViewRepresentable pattern

sequenceDiagram
    participant SwiftUI
    participant MyMetalView as MyMetalView<br/>(UIViewRepresentable)
    participant MTRenderer as MTRenderer<br/>(MTKViewDelegate)
    participant MTKView

    SwiftUI->>MyMetalView: makeCoordinator()
    MyMetalView-->>SwiftUI: MTRenderer instance

    SwiftUI->>MyMetalView: makeUIView(context:)
    MyMetalView->>MTKView: init(frame:device:)
    MyMetalView->>MTKView: delegate = context.coordinator
    MyMetalView-->>SwiftUI: MTKView

    loop every frame
        MTKView->>MTRenderer: draw(in:)
    end

The tutorials use UIViewRepresentable to embed the MTKView (a UIKit class) into SwiftUI:

struct MyMetalView: UIViewRepresentable {
    typealias UIViewType = MTKView

    func makeUIView(context: Context) -> MTKView { … }
    func updateUIView(_ uiView: MTKView, context: Context) { }

    // the coordinator IS the renderer / MTKViewDelegate
    func makeCoordinator() -> MyRenderer { … }
}

The Coordinator pattern lets the renderer own the MTKView delegate without the view being recreated on every SwiftUI update.


Rendering fundamentals

The vocabulary you’ll see everywhere

Vertex β€” a single point in 3D space, bundled with extra attributes like a color, normal, or texture coordinate. A mesh is just a list of vertices (and usually an index list connecting them into triangles).

Triangle β€” the fundamental drawing primitive. Everything rendered is decomposed into triangles because they are always flat and cheap to rasterize.

Fragment β€” a candidate pixel produced during rasterization. Once a triangle is projected onto the screen, the GPU fills it with fragments β€” one per screen pixel covered β€” each carrying values interpolated from the three vertices.

Rasterization β€” the hardware step that converts a projected triangle into a grid of fragments. Metal handles this automatically between the vertex shader and the fragment shader; you don’t write rasterization code yourself.

Shader β€” a small program written in Metal Shading Language (MSL) that runs on the GPU. A vertex shader transforms each vertex’s position. A fragment shader decides the final color of each fragment.

Draw call β€” a CPU command that says β€œprocess this batch of vertices using this pipeline.” Every drawPrimitives or drawIndexedPrimitives call in the tutorials is one draw call.

Framebuffer / drawable β€” the image the GPU is currently writing into. When a frame is complete, Metal presents the drawable to the screen.

Coordinate spaces

Vertices travel through a chain of coordinate spaces before reaching the screen:

Model Space β†’ World Space β†’ View Space β†’ Clip Space β†’ NDC β†’ Screen Space
              ↑              ↑             ↑
           model matrix   view matrix  projection matrix
Space What it means
Model Space Coordinates local to the mesh (origin at the object’s center)
World Space Mesh placed in the scene β€”model matrix applied
View Space Scene seen from the camera β€”view matrix applied
Clip Space After projection;w encodes depth; GPU clips triangles here
NDC After Γ· w: X and Y in [βˆ’1, +1], Z in [0, 1] (Metal convention)
Screen Space Viewport transform maps NDC coordinates to pixel positions

The MVP matrix (Model Γ— View Γ— Projection) is combined on the CPU into one matrix. The vertex shader multiplies each vertex position by it once β€” collapsing three transforms into a single GPU multiply.


Metal object hierarchy

Every Metal object is created by something above it in this tree. The device represents the GPU and is the root of everything β€” nothing works without it.

MTLDevice                        ← represents the GPU
β”œβ”€β”€ MTLLibrary                   ← compiled Metal shader library (.metallib)
β”‚   └── MTLFunction              ← one vertex, fragment, or kernel function
β”œβ”€β”€ MTLCommandQueue              ← persistent queue; submits frames to the GPU
β”‚   └── MTLCommandBuffer         ← one frame's worth of recorded commands
β”‚       β”œβ”€β”€ MTLRenderCommandEncoder    ← encodes draw calls for a render pass
β”‚       β”œβ”€β”€ MTLComputeCommandEncoder   ← encodes GPU compute kernels
β”‚       └── MTLBlitCommandEncoder      ← encodes copy/blit operations
β”œβ”€β”€ MTLRenderPipelineState       ← compiled vertex + fragment pair (expensive)
β”œβ”€β”€ MTLDepthStencilState         ← depth and stencil test configuration
β”œβ”€β”€ MTLBuffer                    ← raw GPU memory (vertices, uniforms, indices…)
β”œβ”€β”€ MTLTexture                   ← image data on the GPU
└── MTLHeap                      ← arena allocator for buffers and textures

Object lifetime and creation cost

Object Lifetime Cost to create Typical pattern
MTLDevice App lifetime Free Create once at startup
MTLLibrary App lifetime Disk I/O Create once, cache
MTLFunction App lifetime Very low Create once per function
MTLCommandQueue App lifetime Low Create once
MTLRenderPipelineState App lifetime High (GPU compile) Create once, never recreate per-frame
MTLDepthStencilState App lifetime Low Create once
MTLBuffer As needed Medium Pool or reuse where possible
MTLTexture As needed Medium Avoid allocating per-frame
MTLHeap As needed Medium Allocate once; suballocate inside
MTLCommandBuffer Per frame Very low New each frame; commit to queue
MTLRenderCommandEncoder Per render pass Very low Create inside draw function

Apple documentation reference

Every key Metal object used across these tutorials, with links to the official Apple docs.

Object Docs Role Tutorials
MTLDevice β†— Represents the GPU; factory for all other objects All
MTLCommandQueue β†— Persistent queue; submits command buffers to the GPU All
MTLCommandBuffer β†— One frame’s recorded commands; committed to the queue All
MTLRenderCommandEncoder β†— Records draw calls inside a render pass All
MTLRenderPipelineState β†— Compiled vertex + fragment pair; expensive β€” create once All
MTLRenderPipelineDescriptor β†— Configuration struct used to build a pipeline state All
MTKView β†— Metal-backed view; manages the drawable and drives the render loop All
MTKViewDelegate β†— Two callbacks:drawableSizeWillChange + draw(in:) All
MTLLibrary β†— Container for compiled Metal shader functions All
MTLFunction β†— A single vertex, fragment, or kernel shader function All
MTLViewport β†— Maps NDC coordinates to a pixel rectangle on screen 1
MTLRenderPassDescriptor β†— Describes load/store actions for each color and depth attachment All
MTLDepthStencilState β†— Depth test configuration: comparator, write-enabled 2–6
MTLDepthStencilDescriptor β†— Configuration struct used to build a depth/stencil state 2–6
MDLAsset β†— Loads 3D geometry files (OBJ, USDZ…) via Model I/O 2
MTKMesh β†— Metal-ready vertex and index buffers from a Model I/O asset 2
MTLTexture β†— Image on the GPU; used for render targets, shadow maps, GBuffer layers 3–6
MTLTextureDescriptor β†— Specifies format, dimensions, and usage flags for a texture 3–6
MTLHeap β†— Arena allocator; one useHeap call covers all suballocated resources 6
MTLIndirectCommandBuffer β†— GPU-writable draw call buffer enabling GPU-driven rendering 6
MTLArgumentEncoder β†— Encodes argument buffers for bindless/Tier-2 resource binding 6
MTLComputeCommandEncoder β†— Records compute dispatches (e.g., fills the ICB per submesh) 6
MTLBlitCommandEncoder β†— Copies data between resources (staging buffer β†’ private texture) 6