Article

Setting Up a Command Structure

Discover how Metal executes commands on a GPU.

Overview

To get the GPU to perform work on your behalf, you send commands to it. A command performs the drawing, parallel computation, or resource management work your app requires. The relationship between Metal apps and a GPU is that of a client-server pattern:

  • Your Metal app is the client.

  • The GPU is the server.

  • You make requests by sending commands to the GPU.

  • After processing the commands, the GPU can notify your app when it's ready for more work.

Figure 1

Client-server usage pattern when using Metal.

Flow chart showing a Metal app's command processing cycle. At left, the Metal app, labeled 'Client,' issues a command, labeled 'Request', at top. At right, the GPU, labeled 'Server,' broadcasts a completion notification, labeled 'Response,' at bottom. Lines connecting the four subjects in a clockwise direction create a closed loop.

To send commands to a GPU, you add them to a command buffer using a command encoder object. You add the command buffer to a command queue and then commit the command buffer when you're ready for Metal to execute the command buffer's commands. The order that you place commands in command buffers, enqueue and commit command buffers, is important because it effects the perceived order in which Metal promises to execute your commands.

The following sections cover the steps to set up a working command structure, ordered in the way you create objects to interact with Metal.

Make Initialization-Time Objects

You create some Metal objects at initialization and normally keep them around indefinitely. Those are the command queue, and pipeline objects. You create them once because they're expensive to set up, but once initialized, they're fast to reuse.

Make a Command Queue

To make a command queue, call the device's makeCommandQueue() function:

commandQueue = device.makeCommandQueue()

Because you typically reuse the command queue, make a strong reference to it. You use the command queue to hold command buffers, as seen here:

Figure 2

Your app's command queue.

Diagram showing a command queue's relationship to the command buffers it contains. A train car representing a command queue contains three box cars representing command buffers, numbered ascending. An arrow pointing to the track's right indicates the processing order of a command queue's buffers occurs ascending.

Make One or More Pipeline Objects

A pipeline object tells Metal how to process your commands. The pipeline object encapsulates functions that you write in the Metal shading language. Here's how pipelines fit into your Metal workflow:

  • You write Metal shader functions that process your data.

  • Create a pipeline object that contains your shaders.

  • When you're ready to use it, enable the pipeline.

  • Make draw, compute, or blit calls.

Metal doesn't perform your draw, compute, or blit calls immediately; instead, you use an encoder object to insert commands that encapsulate those calls into your command buffer. After you commit the command buffer, Metal sends it to the GPU and uses the active pipeline object to process its commands.

Figure 3

The active pipeline on the GPU containing your custom shader code that processes commands.

Flow chart showing the process by which Metal process commands using the active pipeline. At left, the Metal app issues commands to the GPU, at center. The GPU contains the active pipeline object, which contains you custom shader code. A track flows out from the GPU to an endpoint that represents the command results.

Issue Commands to the GPU

With your command queue and pipeline(s) set up, it's time for you to issue commands to the GPU. Here's the process you follow:

  1. Create a command buffer.

  2. Fill the buffer with commands.

  3. Commit the command buffer to the GPU.

If you're performing animation as part of a rendering loop, you do this for every frame of the animation. You also follow this process to execute one-off image processing, or machine learning tasks.

The following subsections walk you through these steps in detail.

Create a Command Buffer

Create a command buffer by calling makeCommandBuffer() on the command queue:

Listing 2

Creating a command buffer.

guard let commandBuffer = commandQueue.makeCommandBuffer() else { 
    return 
}

For single-threaded apps, you create a single command buffer. Figure 4 shows the relationship between commands and their command buffer:

Figure 4

A command buffer's relationship to the commands it contains.

Diagram showing a command's relationship to the command buffer that contains it. A box car labeled 'Command buffer' contains a series of boxes representing commands, labeled 'c', which are numbered ascending to indication their insertion order, from left to right.

Add Commands to the Command Buffer

When you call task-specific functions on an encoder object–like draws, compute or blit operations–the encoder places commands corresponding to those calls in the command buffer. The encoder encodes the commands to include everything the GPU needs to process the task at runtime. Figure 5 shows the workflow:

Figure 5

Command encoder inserting commands into a command buffer as the result of a draw.

Flow chart showing the series of events that effect command creation and placement of a command into a command buffer. At left, a Metal app makes a draw call to a command encoder, at middle. The command encoder responds by sending a command to the command buffer, at right.

You encode actual commands with concrete subclasses of MTLCommandEncoder, depending on your task:

See Using a Render Pipeline to Render Primitives for a complete rendering example. See Hello Compute for a complete parallel processing example.

Commit a Command Buffer

To enable your commands to run, you commit the command buffer to the GPU:

commandBuffer.commit()

Committing a command buffer doesn't run its commands immediately. Instead, Metal schedules the buffer's commands to run only after you commit prior command buffers that are waiting in the queue. If you haven't explicitly enqueued a command buffer, Metal does that for you once you commit the buffer.

You don't reuse a buffer after it's committed, but you can opt into notification of its scheduling, completion, or query its status.

The promise upheld by Metal is that the perceived order in which commands are executed is the same as the way you ordered them. While Metal might reorder some of your commands before processing them, this normally only occurs when there's a performance gain and no other perceivable impact.

See Also

Command Setup

protocol MTLCommandQueue

A queue that organizes command buffers to be executed by a GPU.

protocol MTLCommandBuffer

A container that stores encoded commands for the GPU to execute.

protocol MTLCommandEncoder

An encoder that writes GPU commands into a command buffer.

Counter Sampling

Retrieve information about how the GPU executed your commands.