Hide chapters

Metal by Tutorials

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section I: The Player

Section 1: 8 chapters
Show chapters Hide chapters

Section III: The Effects

Section 3: 10 chapters
Show chapters Hide chapters

23. Debugging & Profiling
Written by Marius Horga

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Debugging and Profiling are must-have skills for optimizing the performance of your projects, and Apple has provided developers with several fantastic optimization tools to achieve this. In this chapter, you’ll look at:

  1. Debugging: Locate hard-to-find bugs using the GPU debugger.
  2. Profiling: Monitor your app’s performance with Instruments and other tools.

Time to go on a bug hunt!


Rather than trying to guess the truth, it’s always more useful to capture a GPU frame and examine it for clues.

The aim of this section is to render a scene similar to this:

In the starter folder, open the Debugging project. Build and run, and you’ll notice the rocks and grass are missing from the scene.

You’ll look into potential causes for why that happened, shortly.

Launch the Capture GPU Frame tool by clicking the camera button in the debug bar.

Debug navigator

After Xcode finishes capturing the GPU frame, it displays the debugger windows.

Central pane

The top middle area of the captured frame has the central pane that normally opens to the Dependency Viewer — a nice summary of the current command buffer.

Debug area

The bottom area is the Debug area, and it has two larger areas — GPU States and Console — as well as the debug bar that hosts the Debug Shader and the Reload Shader buttons; You’ll use these a lot in your debugging career.

The geometry viewer

While still having drawIndexedPrimitives for the Rocks group selected, click the Debug Shader icon on the debug bar, and select the Vertex pane. This is the Geometry Viewer tool.

VertexIn vertexIn = in[offset];
VertexIn vertexIn = in[vertexID + offset];

out.uv = vertexIn.uv;

The pixel inspector

With the draw call in the Rocks group still selected, click Debug Shader again. This time, you’ll use the Inspect Pixels tool to look at the scene. You can, once again, confirm with the magnifier that the textures are indeed applied to rocks, because you can see other colors as well besides shades of grey.

The shader debugger

There are two ways to navigate to the fragment shader code. With the magnifier placed on an active fragment — i.e., one that is in the group currently being drawn — either:

float3 sunlight = normalize(in.worldNormal);
float3 normal = normalize(in.worldNormal);
float diffuseIntensity = saturate(dot(lightDirection, 
float diffuseIntensity = saturate(dot(lightDirection, normal));

float4 color = mix(baseColor * 1.5, baseColor * 0.5, 
float4 color = mix(baseColor * 0.5, baseColor * 1.5, 

GPU frame capture

There’s one more debugging feature that you’ll love and use a lot. You can trigger the GPU Capture Frame tool to start at a breakpoint!


Always profile early and do it often.

GPU history

GPU history is a tool provided by the macOS operating system via its Activity Monitor app, so it is not inside Xcode. It shows basic GPU activity in real time for all of your GPUs. If you’re using eGPUs, it’ll show activity in there too.

The GPU report

Build and run your app again, then capture a GPU frame. On the Debug navigator, click FPS gauge on the left side.

The shader profiler

This is perhaps the most useful profiling tool for the shader code you are writing. It has nothing to do with the rendering code the CPU is setting up, or the passes you run or the resources you’re sending to the GPU. This tool tells you how your MSL code is performing line-by-line and how long it took to finish.

GPU Counters and Memory

The GPU Counters information panel is another profiling tool you can use for optimizing the performance of your command encoders.

Pipeline statistics

Pipeline statistics is yet another profiling tool that tells you the number of instructions each of the GPU activities of your draw call is using.

Dependency viewer

The Dependency Viewer is one of the new tools introduced at WWDC 2018, along with the Shader Debugger and the Geometry Viewer.

Metal System Trace

The last and most essential profiling tool is the Metal System Trace (MST) which is a specialized Instruments template for Metal. There are two ways of launching an MST session:

setupGrass(instanceCount: 50000, width: 10, depth: 10)
setupGrass(instanceCount: 400000, width: 10, depth: 10)

Surface was displayed for 33.33ms on Display.
metalView.preferredFramesPerSecond = 30

Where to go from here?

The path to optimal performance is not trivial, and it’s going to be a journey full of trial and error experiments. Where debugging is a science, profiling is a work of art. Experiment, take a step back, look at it, go back and tweak some more. In the end, it’s all going to be worth the effort.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2023 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now