GPU Profiling in DirectX Using Timestamp Queries

Timestamp queries in DirectX provide a hardware-level mechanism for measuring how long specific rendering and compute tasks take to execute directly on the GPU. Because the CPU and GPU operate asynchronously, standard CPU timers cannot accurately measure GPU workloads. Timestamp queries solve this by writing GPU clock ticks into memory at designated points in the command stream, allowing developers to isolate performance bottlenecks, measure frame-stage durations, and optimize rendering pipelines with high precision.

The Purpose of Timestamp Queries

In modern graphics APIs like DirectX 11 and DirectX 12, the CPU records commands into command lists and submits them to a queue, while the GPU processes them at a later time. Using CPU-side timers to profile draw calls or compute dispatches measures only submission time, driver overhead, and synchronization latency—not actual GPU execution time.

Timestamp queries place measurement markers directly within the GPU's command pipeline. When the GPU reaches a timestamp query marker, it records its current internal clock counter. By capturing timestamps before and after a specific operation or render pass, developers can calculate the exact duration the GPU spent processing that workload.

How Timestamp Queries Work in DirectX

Timestamp profiling relies on writing query commands into the command stream and reading the results back once execution completes:

  1. Query Heap Allocation: In DirectX 12, a query heap is created with the type D3D12_QUERY_HEAP_TYPE_TIMESTAMP. This serves as the storage allocation for a predetermined number of timestamp recordings.
  2. Command Insertion: The developer inserts queries around the targeted operations within a command list using EndQuery (timestamps in DirectX only support EndQuery because a timestamp represents an instantaneous point in time rather than a scoped range).
  3. Resolving Data: Before the CPU can access the recorded ticks, a command is issued (ResolveQueryData) to extract the raw query results from the query heap and write them into a standard ID3D12Resource buffer.
  4. Readback and Calculation: The resolve buffer is mapped to the CPU (or used by the GPU in shader pipelines) to calculate the delta between the start and end ticks.

Converting Clocks to Real-World Time

The raw values retrieved from timestamp queries represent clock cycles rather than seconds or milliseconds. To convert these ticks into actionable time units, DirectX provides command queue frequency data.

In DirectX 12, calling ID3D12CommandQueue::GetTimestampFrequency returns the clock frequency of the GPU command queue in ticks per second (Hertz). In DirectX 11, this information is provided via a D3D11_QUERY_TIMESTAMP_DISJOINT query.

The elapsed execution time is calculated using the following formula:

\[\text{Time (milliseconds)} = \frac{\text{End Timestamp} - \text{Start Timestamp}}{\text{Timestamp Frequency}} \times 1000\]

Key Functions and Benefits in Profiling

  • Granular Bottleneck Identification: Developers can isolate individual draw calls, compute shaders, post-processing effects, or shadow map passes to determine precisely which stage of the pipeline consumes the most frame budget.
  • Elimination of Pipeline Bubbles: Comparing timestamp sequences reveals when the GPU is idling between tasks, exposing pipeline stalls caused by resource dependencies or synchronization barriers.
  • Zero Host Interference: Timestamp queries do not force CPU-GPU synchronization or flush the pipeline, ensuring profiling accurately reflects real-world runtime conditions.
  • Custom In-Engine Profilers: By combining timestamp queries with ring buffers, engines can display real-time performance graphs and hierarchical GPU profiling overlays without relying solely on external tools.