Heads up... You're reading this book for free, with parts of this chapter shown beyond this point astext.
You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.
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:
- Debugging: Locate hard-to-find bugs using the GPU debugger.
- 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.
After Xcode finishes capturing the GPU frame, it displays the debugger windows.
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.
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, sunlight));
float diffuseIntensity = saturate(dot(lightDirection, normal));
float4 color = mix(baseColor * 1.5, baseColor * 0.5, diffuseIntensity);
float4 color = mix(baseColor * 0.5, baseColor * 1.5, diffuseIntensity);
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 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 is yet another profiling tool that tells you the number of instructions each of the GPU activities of your draw call is using.
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.