Tutorial: Adding Performance Instrumentation to your game by using the PIX APIs
Learn more about PIX Events and how they can help you improve your game’s performance.
Recently, our team created a series of videos to introduce developers to PIX, the game-focused profiling tool from Microsoft for Windows games. PIX is the profiler that’s used by game studios to optimize the PC build of their 64-bit, C++, and DirectX 12 games. Developers can use PIX to solve lots of performance issues that are common in games, including issues related to the GPU, CPU, memory, or file IO.
In this video, I'll go through the steps that are required to add performance instrumentation to your game by calling the PIX APIs. Generally speaking, the performance data that’s available in PIX Timing Captures can be grouped into two categories. The first category is the set of data that you provide by instrumenting your game by following the steps that are provided in this video. The second category is the data that PIX collects from the system while the capture is running. The power of Timing Captures comes from the fact that data from both of these sources is combined into a single capture type and presented in the order they appear.
We'll focus on PIX events because events are the most common type of instrumentation to add. Other types of instrumentation, such as PIX markers and counters, are also important but are often used less frequently. Adding the right amount of instrumentation in the form of PIX events helps you get the most out of Timing Captures. PIX events are used to define blocks of time that are specific to your game.
Watch the video below, or head to the Microsoft Game Dev YouTube channel to open it in a new window.
The PIX APIs are provided by a NuGet package called WinPIXEventRuntime. The NuGet package includes a header file that defines the APIs and a runtime .dll file that you distribute with your game. You can find the documentation for the APIs here.
The first step in adding PIX instrumentation is to add the WinPIXEventRuntime NuGet package to the project. To do so, right-click your project in Solution Explorer and choose "Manage NuGet Packages" from the context menu. Next, use the Browse tab and search for WinPIXEventRuntime, and then select Install to add the NuGet package to your project. Finally, open one of your project's common header files and include the pix3.h header file from the NuGet package. If you’re using a release build of your title, you’ll also want to define the USE_PIX preprocessor symbol.
Now we can add some PIX events.
PIX events are used to mark the beginning and end of any piece of code that's meaningful to you as a game developer, and it will likely be interesting to profile. The PIXBeginEvent API marks the beginning of the event; the PIXEndEvent API marks the end. As an example, it's common to mark the beginning and end of your game's main loop, represented in this case by the MainLoop function.
There are various overloads to PIXBeginEvent, but they all have two parameters in common: a color and a string. They are used to identify the event in the PIX UI. PIX events can be named by using a format string. This is handy for doing things like differentiating between frames by using a number. Note that PIX events can be nested at any arbitrary depth. Nested events are displayed as a flame graph in the PIX UI. The only restriction to keep in mind is that a PIX event must start and end on the same thread.
We used PIXBeginEvent and PIXEndEvent to instrument our main loop. There's a shortcut, however, that has the convenient side effect of ensuring that event beginnings and endings are always paired. The shortcut is to use the PIXScopedEvent API. It takes the same parameters as PIXBeginEvent and causes the beginning of the event to start at the beginning of the function. PIXScopedEvent also causes the end event to occur just before the function exists. In this case, I'll select one of the default colors and then name my event "Update".
The events that I define in the video are referred to as CPU events because they mark the beginning and end of interesting pieces of code that are running on a CPU thread. PIX also includes the concept of a GPU event. This event is commonly used in CPU code that is submitting work to the GPU, which is common on a render thread. PIX GPU events show you when the CPU submitted work to the GPU and when that work was executed by the GPU.
A GPU event is defined by using PIXBeginEvent API but with an extra parameter: a Direct3D command queue or command list. Note that the call to PIXEndEvent for a GPU event must include either the same command queue, or a command list submitted to the same queue.
In future videos, we'll see how this instrumentation is displayed in PIX, and we'll use this instrumentation to help us solve some of the most common performance issues that you're likely to see in games.