Article

Developing and Debugging Metal Shaders

Step through your app's shaders to inspect their variables during execution, and use a live preview to enhance development.

Overview

The shader debugger brings common debugger features to your shader development by enabling you to step through code line by line, and verify the results of your algorithm. Xcode halts on a single frame of your app that you choose, and gives you a live preview that updates as you make changes to your shader code.

Configure Your Build to Include Source Code

To use the shader debugger, Xcode looks to your compiled .metallib for source code. You enable this in your project build settings by changing "Produce debugging information" for Debug to "Yes, include source code".

Screenshot of the "Produce debugging information" build setting set to include shader source.

Apps shipped to customers shouldn't contain debugging information, so set Release to No.

Capture a Frame

The shader debugger works in conjunction with Xcode's Metal frame capture. Build and run your project, then click the camera button on Xcode's debugging toolbar.

Screenshot of Xcode's debug bar with the camera button marked by a callout.

For more information about frame capture, see Frame Capture Debugging Tools.

Unlike a conventional debugger, the shader debugger doesn't allow you to set breakpoints on code running on the GPU. However, you can start the shader debugger from your host app at a specific time that you choose. See Capturing a Frame Programmatically.

Inspect and Debug a Vertex Function

To debug a particular vertex function, first you select a primitive that was rendered in the frame. Using Xcode's geometry viewer, you find a part of the draw call's mesh that looks incorrect and then use the shader debugger to figure out why.

When the frame capture completes, Xcode shows the results in the Debug navigator. Expand a render command encoder in the call list and select a draw call.

Screenshot of the API call list with a draw call selected.

In the draw's bound resources, double-click Geometry.

Screenshot of the draw call's bound resources with the Geometry item selected.

Xcode displays a wireframe of the draw call in the geometry viewer.

Screenshot of the geometry viewer populated with the selected draw call's vertex data.

Say for example, one primitive in the wireframe doesn't align exactly with its adjacent primitives, and you want to understand why. Click to select this primitive.

Screenshot of the geometry viewer wireframe with a primitive selected and marked by a callout.

Click the Debug button.

Screenshot of the geometry viewer toolbar with the Debug button marked by a callout.

The shader debugger starts and displays the vertex function that rendered the selected primitive in the source code view.

Screenshot of Xcode's source code view populated with a vertex shader.

Inspect and Debug a Fragment Function

To debug a particular fragment function, first you select a pixel that was rendered in the frame.

When the frame capture completes, Xcode shows the results in the Debug navigator. Expand a render command encoder in the call list and select a draw call.

Screenshot of the API call list with a draw call selected.

Xcode displays the draw call's attachments in the assistant editor.

Screenshot the assistant editor. At top, the attachments option in the assistant editor breadcrumb is marked by a callout. At bottom, the attachment for the selected draw is displayed.

Press and hold an area within the attachment, and Xcode displays the targeting reticle.

Screenshot of the selected draw's attachment with the targetting reticle hovering over a particular pixel.

Adjust the location of the targeting reticle to the pixel you want to debug, and click the Debug button.

Screenshot of the attachment view bar with the Debug button marked by a callout.

The shader debugger starts and displays the fragment function that rendered the selected pixel in the source code view.

Screenshot of the source code view populated with the fragment shader that output the selected pixel.

Step Through Shader Code Line by Line

When you have geometry that's not placed where you expect, or a pixel that's displaying the wrong color, you step through your shader code line by line to understand why. As with a traditional debugger, you check the value of variables each step of the way until you see an unexpected value that indicates the cause of the problem.

Click the top line in the call list.

Screenshot of the call list with the fragment shader's entry point selected.

The line you select in the call list determines which line of code the shader debugger is currently processing. This line is also referred to as the location of the execution playhead. You use keyboard arrow keys to advance the playhead through your code, one line at a time.

Arrow key

Stepping direction

Down arrow

Step forward

Up arrow

Step backward

Right arrow

Step in

Left arrow

Step out

As you step in the call list (marked by callout 1 in the following image), the playhead follows along in the source code view (marked by callout 2).

Screenshot of stepping through the debugger. At left, the selection in the ROI list reflects the same line of code that's selected in the source view, at right.

Alternatively, you can set the debugger playhead by clicking any line in the source code view, and Xcode selects the corresponding function in the call list.

Inspect Variable Values

The shader debugger displays variable views in the sidebar that show the results of each line of code.

Screenshot of the source code view with the variables sidebar marked by a callout.

The values in this view reflect the state of the selected data at the point in time that's determined by the execution playhead. The live nature of this view is particularly useful when you step through loop iterations.

To get more information about a calculated value, click the box to its right.

Screenshot of a variable in the shader source right pane with its inspection block marked by a callout.

Xcode reveals an inspection pane below its line in the source code view.

Screenshot of an inspected color sample's graphical rendering.

Use this rendering to visually check the variable is the value you expect. For graphical data, Xcode's visualization can be easier to verify than numeric data alone.

If a variable has nested properties, you can disclose them in a cascading fashion as seen in the following image.

Screenshot showing the expanded view of a variable's nested properties.

This enables you to dive in to an object's data by showing you more than Xcode fits in the right sidebar.

Watch Variables Change Over Time

The live views provide numeric and visual feedback about how your shader variables change over time. If you inspect a color sample (callout 1 in the following image) before a line of code that changes its contents, you can see its updated value in a subsequent assignment (callout 2).

Two variable inspection panels are expanded to illustrate an obvious visual change from before and after the modifying statement executed.

Update Shaders Live

After changing a shader you can update the captured frame with the new source code by clicking the refresh icon.

Screenshot of the shader debug bar with the refresh icon marked by a callout.

In turn, Xcode:

  • Redraws the application window.

  • Updates variable views to show their new values.

  • Redraws attachments in the assistant editor.

Updating shaders maintains your place in the captured frame, which provides an interactive environment to enhance your shader development and debugging.

See Also

Essentials

Optimizing Performance with the Shader Profiler

Discover which lines of shader code take the longest to complete, identify their primary GPU activities, and tune your shaders accordingly.